Java Spring Boot 2.3和Spring Security 5-在一个模式中支持UserDetailsService和OAuth2
我正在使用SpringBoot2.3.x、SpringSecurity5和Thymeleaf构建一个JavaWebApp,运行在Java11上 该应用程序需要支持某种类型的用户帐户。作为起点,我遵循了John Thompson(又称Spring框架大师)在其“课程”中使用的方法。John的方法使用Spring数据JPA和HTTP基本身份验证,其中我实现了Spring接口Java Spring Boot 2.3和Spring Security 5-在一个模式中支持UserDetailsService和OAuth2,java,spring-boot,spring-mvc,spring-security,spring-security-oauth2,Java,Spring Boot,Spring Mvc,Spring Security,Spring Security Oauth2,我正在使用SpringBoot2.3.x、SpringSecurity5和Thymeleaf构建一个JavaWebApp,运行在Java11上 该应用程序需要支持某种类型的用户帐户。作为起点,我遵循了John Thompson(又称Spring框架大师)在其“课程”中使用的方法。John的方法使用Spring数据JPA和HTTP基本身份验证,其中我实现了Spring接口UserDetailsService,并允许应用程序在HTTP基本身份验证期间根据需要从数据库加载用户凭据(用户名、密码、角色、
UserDetailsService
,并允许应用程序在HTTP基本身份验证期间根据需要从数据库加载用户凭据(用户名、密码、角色、权限)。这一切都很好。因为我将每个用户的角色/权限存储在数据库中,所以我拥有完全的控制权,可以将它们与Spring安全方法级别的注释一起使用,如:@PreAuthorize(“hasAuthority('user.details.read')”)
。同样,这一切都很好
从课程材料来看,John方法的问题在于,我仅限于HTTP Basic和存储/管理所有用户密码
昨天,我试用了SpringSecurity5的OAuth2.0特性,即“使用Facebook登录”。我使用了中的一些代码开始。单独来说,我的应用程序可以很好地将用户验证为Facebook成员不幸的是,这提供了另一种@AuthenticationPrincipal
对象,其中包含仅与Facebook相关的角色和权限。
问题
我现在有两种断开连接的用户类型:
- 我的应用程序数据库存储分配给每个用户的角色/权限
- 应用程序将支持通过HTTP Basic或OAuth2(最初到Facebook)进行身份验证,但我的应用程序数据库将提供角色/权限
- 每个用户的“唯一标识符”将是他们的电子邮件地址(FacebookOAuth2将此作为一个属性提供),因此我希望这可以用于关联HTTP Basic和OAuth2身份验证对象
- 用户可以为其帐户设置HTTP Basic和OAuth2,如果是这样,则可以使用任一方法登录。无论哪种方式,他们在我的应用程序中仍然具有相同的角色/权限
UserDetailsService
相关的部分以及为该服务提供服务的JPA实体已经运行良好。我的问题本质上是“如何将OAuth2合并到已经工作的内容中?”
MyWebSecurityConfigureAdapter
实现类
我的登录表单的控制器,支持“使用Facebook登录”或HTTP Basic
MyUserDetailsService
实现类(用于HTTP基本身份验证)
我的UserRepository
定义
schema.sql
-在Spring Security中创建OAuth2类用于持久令牌存储的表
CREATE TABLE oauth2_authorized_client (
client_registration_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
access_token_type varchar(100) NOT NULL,
access_token_value blob NOT NULL,
access_token_issued_at timestamp NOT NULL,
access_token_expires_at timestamp NOT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
created_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (client_registration_id, principal_name)
);
当我使用自己的Facebook帐户通过Facebook OAuth2登录时,myLoginFormController
的日志输出如下所示:
[INFO ] LoginFormController - USER AUTHENTICATED WITH OAUTH2
[INFO ] LoginFormController - auth token 'authorized client registration id': [facebook]
[INFO ] LoginFormController - auth token 'name': [10139295061993788]
[INFO ] LoginFormController - oauth2User 'name': [10139295061993788]
[INFO ] LoginFormController - oauth2User 'id' attribute value: [10139295061993788]
[INFO ] LoginFormController - oauth2User 'name' attribute value: [Jim Tough]
[INFO ] LoginFormController - oauth2User 'email' attribute value: [jim@jimtough.com]
我还看到了我的另一个侦听器类的日志输出:
[INFO ] AuthenticationEventLogger - principal type: OAuth2LoginAuthenticationToken | authorities: [ROLE_USER, SCOPE_email, SCOPE_public_profile]
权限ROLE\u USER、SCOPE\u email、SCOPE\u public\u profile
在我的应用程序上下文中毫无意义
我相信您正在寻找的是
授权权威映射器
您注册了一个bean,它将您的权限映射到要使用的角色
@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this.userAuthoritiesMapper())
...
)
);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
}
});
return mappedAuthorities;
};
}
}
您可以阅读更多信息。谢谢@Toerktumlare!我来看看你推荐的。如果这行得通,我会回来接受你的回答。
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
public class User implements UserDetails, CredentialsContainer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String username;
private String password;
@Singular
@ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
@JoinTable(name = "user_role",
joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")})
private Set<Role> roles;
@Transient
public Set<GrantedAuthority> getAuthorities() {
return this.roles.stream()
.map(Role::getAuthorities)
.flatMap(Set::stream)
.map(authority -> {
return new SimpleGrantedAuthority(authority.getPermission());
})
.collect(Collectors.toSet());
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
@Builder.Default
private Boolean accountNonExpired = true;
@Builder.Default
private Boolean accountNonLocked = true;
@Builder.Default
private Boolean credentialsNonExpired = true;
@Builder.Default
private Boolean enabled = true;
@Override
public void eraseCredentials() {
this.password = null;
}
@CreationTimestamp
@Column(updatable = false)
private Timestamp createdDate;
@UpdateTimestamp
private Timestamp lastModifiedDate;
}
CREATE TABLE oauth2_authorized_client (
client_registration_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
access_token_type varchar(100) NOT NULL,
access_token_value blob NOT NULL,
access_token_issued_at timestamp NOT NULL,
access_token_expires_at timestamp NOT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
created_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (client_registration_id, principal_name)
);
[INFO ] LoginFormController - USER AUTHENTICATED WITH OAUTH2
[INFO ] LoginFormController - auth token 'authorized client registration id': [facebook]
[INFO ] LoginFormController - auth token 'name': [10139295061993788]
[INFO ] LoginFormController - oauth2User 'name': [10139295061993788]
[INFO ] LoginFormController - oauth2User 'id' attribute value: [10139295061993788]
[INFO ] LoginFormController - oauth2User 'name' attribute value: [Jim Tough]
[INFO ] LoginFormController - oauth2User 'email' attribute value: [jim@jimtough.com]
[INFO ] AuthenticationEventLogger - principal type: OAuth2LoginAuthenticationToken | authorities: [ROLE_USER, SCOPE_email, SCOPE_public_profile]
@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this.userAuthoritiesMapper())
...
)
);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
}
});
return mappedAuthorities;
};
}
}
@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login(withDefaults());
}
@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
...
}
}