OAuth2 客户端委托人在通过其他自定义授权服务器(SpringBoot2 和 OAuth2)进行身份验证时没有 GrantedAuthorities
OAuth2 Client Principal do not have GrantedAuthorities when authenticated by Other Custom Authorization Server (SpringBoot2 & OAuth2)
我正在使用 Spring Boot2 作为框架,Thymeleaf 作为模板引擎。
在我的授权服务器中,我将用户 'admin' 添加为 'ROLE_ADMIN'。
但是在客户端应用程序中,当我以 'admin' 身份登录并从 SecurityContextHolder.getContext().getAuthentication()
打印 Authentication
对象时,Granted Authorities
属性 只有 'ROLE_USER'.
以下是我的授权服务器配置。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN");
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("123")).roles("USER");
}
下面是来自 SecurityContextHolder.getContext().getAuthentication()
的日志代码的 Authentication
对象。
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.isAuthenticated());
System.out.println(auth.getAuthorities());
System.out.println(auth.getPrincipal());
结果是
// isAuthenticated()
true
// getAuthorites()
[ROLE_USER]
// getPrincipal()
Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=ROLE_ADMIN}, {authority=ROLE_USER}], ...
以下是我的 thymeleaf 代码。
<div sec:authorize="isAuthenticated()">
Text visible only to authenticated users.
<!-- Principal name -->
Authenticated username:
<div sec:authentication="name"></div>
<div sec:authorize="hasRole('USER')">Text visible to user.</div>
<!-- i cant see this message -->
<div sec:authorize="hasRole('ADMIN')">Text visible to admin.</div>
Authenticated user roles:
<!-- print '[ROLE_USER]' only -->
<div sec:authentication="principal.authorities"></div>
</div>
<div sec:authorize="!isAuthenticated()">Text visible only to
unauthenticated users.
</div>
所以,我想在 thymeleaf 中访问 Principal.UserAttributes.authorities
。
我指的是 OAuth2AuthenticationToken
、OAuth2User.getAttributes()
和 DefaultOAuth2User.toString()
我该怎么做?
您可以将 Principal 作为参数传递给您的控制器,例如
public String myController(Principal principal) {
...
}
您还必须自己将权限映射到授予的权限,例如使用来自 Spring 的 AuthoritiesExtractor 接口,这是一个示例:Link from Baeldung
使用#authentication 对象
<div th:text="${#authentication.principal.something}">
The value of the "name" property of the authentication object should appear here.
</div>
示例:
<img th:if="${#authentication.principal.image}"
class="img-circle" th:src="${#authentication.principal.image}"
width="100" height="100" alt="place-holder" />
但是先添加这个依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>-latest-version-here-</version>
</dependency>
因为 spring boot
中没有 thymeleaf-starter
我解决了。
在授权服务器中,我是这样配置的。
- 授权服务器
WebSecurityConfigurerAdapter
配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN").authorities("USER", "ADMIN");
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("123")).roles("USER");
}
...
}
下面是我的资源服务器的 /me
映射控制器
- ResourceServer
/me
映射控制器
@RestController
public class UserController {
@RequestMapping("/me")
public Principal user(Principal principal) {
return principal;
}
}
以下是我客户的 WebSecurityConfigurerAdapter
配置
- 客户端
WebSecurityConfigurerAdapter
配置
@Configuration
@EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login();
}
在客户端的控制器中,我是这样登录的。
- 在客户端控制器中记录
Principal
@GetMapping("")
public String git1() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getPrincipal());
/** Thymeleaf using this **/
Object authenticationProperty = AuthUtils.getAuthenticationProperty(auth, "principal.attributes['authorities']");
System.out.println(authenticationProperty.toString());
return VIEW_PATH + "git1";
}
结果如下
Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=USER}, {authority=ADMIN}], details={remoteAddress=127.0.0.1, sessionId=null, tokenValue=82a7a532-a31e-4d0a-bd83-f15a9cbea3bc, tokenType=Bearer, decodedDetails=null}, authenticated=true, userAuthentication={authorities=[{authority=USER}, {authority=ADMIN}], details=null, authenticated=true, principal=admin, credentials=N/A, name=admin}, oauth2Request={clientId=foo, scope=[read], requestParameters={client_id=foo}, resourceIds=[], authorities=[], approved=true, refresh=false, redirectUri=null, responseTypes=[], extensions={}, refreshTokenRequest=null, grantType=null}, clientOnly=false, principal=admin, credentials=, name=admin]
[{authority=USER}, {authority=ADMIN}]
如您所见,我在授权服务器中添加了 'ROLE_USER' 和 'ROLE_ADMIN' 权限。
在资源服务器的 Principal
对象中同时授予 'ROLE_ADMIN' 和 'ROLE_USER'。
但在客户的 Principal
对象中未授予 'ROLE_ADMIN'。只有'ROLE_USER'。
和 Principal.atttibutes['authorities']
有 'USER', 'ADMIN'.
正如@Rahil Husain 所说,有 DefaultOAuth2UserService
并且此服务授予 'ROLE_USER' 仅对 OAuth2User
对象。
首先,我通过 @Componenet
注释(@Bean
也添加了 CustomAuthoritiesExtractor
到客户端。
但这在我的项目中不起作用。
所以,我实现了 CustomOAuth2User
和 CustomOAuth2UserService
。
像这样。
CustomOAuth2User
public class CustomOAuth2User implements OAuth2User {
private List<GrantedAuthority> authorities;
private Map<String, Object> attributes;
private String name;
public CustomOAuth2User(List<GrantedAuthority> authorities, Map<String, Object> attributes) {
this.authorities = authorities;
this.attributes = attributes;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public Map<String, Object> getAttributes() {
if (this.attributes == null) {
this.attributes = new HashMap<>();
this.attributes.put("name", this.getName());
}
return attributes;
}
@Override
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
下面是CustomOAuth2UserService
CustomOAuth2UserService
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
AuthoritiesExtractor authoritiesExtractor = new CustomAuthoritiesExtractor();
List<GrantedAuthority> grantedAuthorityList = authoritiesExtractor.extractAuthorities(oAuth2User.getAttributes());
CustomOAuth2User customOAuth2User = new CustomOAuth2User(grantedAuthorityList, oAuth2User.getAttributes());
customOAuth2User.setName(oAuth2User.getName());
return customOAuth2User;
}
}
下面是我的CustomAuthoritiesExtractor
。此 class 未用作 @Bean
或 @Component
。直接在 CustomOAuth2Service
中用于映射 CustomOAuth2User
对象的权限
CustomAuthoritiesExtractor
public class CustomAuthoritiesExtractor implements AuthoritiesExtractor {
@Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(asAuthorities(map));
}
private String asAuthorities(Map<String, Object> map) {
List<String> authorities = new ArrayList<>();
List<LinkedHashMap<String, String>> authz =
(List<LinkedHashMap<String, String>>) map.get("authorities");
for (LinkedHashMap<String, String> entry : authz) {
authorities.add(entry.get("authority"));
}
return String.join(",", authorities);
}
}
最后,我将客户端的端点更改为使用我的 CustomOAuth2User
和 CustomOAuth2UserService
。
所以,我像这样更改了客户端的 WebSecurityConfigurerAdapter
配置。
@Configuration
@EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login()
/** add this config**/
.userInfoEndpoint()
.customUserType(CustomOAuth2User.class, "teemo")
.userService(this.oauth2UserService());
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return new CustomOAuth2UserService();
}
下面是我的百里香。
- 百里香叶
<div sec:authorize="isAuthenticated()">
Text visible only to authenticated users.
Authenticated username:
<div sec:authentication="name"></div>
<div sec:authorize="hasRole('USER')">hasRole('USER')</div>
<div sec:authorize="hasRole('ROLE_USER')">hasRole('ROLE_USER')</div>
<div sec:authorize="hasRole('ADMIN')">hasRole('ADMIN')</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">hasRole('ROLE_ADMIN')</div>
<!-- TRUE -->
<div sec:authorize="hasAuthority('USER')">hasAuthority('USER')</div>
<div sec:authorize="hasAuthority('ROLE_USER')">hasAuthority('ROLE_USER')</div>
<!-- TRUE -->
<div sec:authorize="hasAuthority('ADMIN')">hasAuthority('ADMIN')</div>
<div sec:authorize="hasAuthority('ROLE_ADMIN')">hasAuthority('ROLE_ADMIN')</div>
</div>
<div sec:authorize="!isAuthenticated()">Text visible only to
unauthenticated users.
</div>
结果如下
Text visible only to authenticated users. Authenticated username:
admin
hasAuthority('USER')
hasAuthority('ADMIN')
和我一样挖坑的朋友,希望对这个问题和答案有所帮助。
但我不知道这是 facto-standard 方式。
只是..现在工作。
我正在使用 Spring Boot2 作为框架,Thymeleaf 作为模板引擎。
在我的授权服务器中,我将用户 'admin' 添加为 'ROLE_ADMIN'。
但是在客户端应用程序中,当我以 'admin' 身份登录并从 SecurityContextHolder.getContext().getAuthentication()
打印 Authentication
对象时,Granted Authorities
属性 只有 'ROLE_USER'.
以下是我的授权服务器配置。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN");
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("123")).roles("USER");
}
下面是来自 SecurityContextHolder.getContext().getAuthentication()
的日志代码的 Authentication
对象。
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.isAuthenticated());
System.out.println(auth.getAuthorities());
System.out.println(auth.getPrincipal());
结果是
// isAuthenticated()
true
// getAuthorites()
[ROLE_USER]
// getPrincipal()
Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=ROLE_ADMIN}, {authority=ROLE_USER}], ...
以下是我的 thymeleaf 代码。
<div sec:authorize="isAuthenticated()">
Text visible only to authenticated users.
<!-- Principal name -->
Authenticated username:
<div sec:authentication="name"></div>
<div sec:authorize="hasRole('USER')">Text visible to user.</div>
<!-- i cant see this message -->
<div sec:authorize="hasRole('ADMIN')">Text visible to admin.</div>
Authenticated user roles:
<!-- print '[ROLE_USER]' only -->
<div sec:authentication="principal.authorities"></div>
</div>
<div sec:authorize="!isAuthenticated()">Text visible only to
unauthenticated users.
</div>
所以,我想在 thymeleaf 中访问 Principal.UserAttributes.authorities
。
我指的是 OAuth2AuthenticationToken
、OAuth2User.getAttributes()
和 DefaultOAuth2User.toString()
我该怎么做?
您可以将 Principal 作为参数传递给您的控制器,例如
public String myController(Principal principal) {
...
}
您还必须自己将权限映射到授予的权限,例如使用来自 Spring 的 AuthoritiesExtractor 接口,这是一个示例:Link from Baeldung
使用#authentication 对象
<div th:text="${#authentication.principal.something}">
The value of the "name" property of the authentication object should appear here.
</div>
示例:
<img th:if="${#authentication.principal.image}"
class="img-circle" th:src="${#authentication.principal.image}"
width="100" height="100" alt="place-holder" />
但是先添加这个依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>-latest-version-here-</version>
</dependency>
因为 spring boot
中没有 thymeleaf-starter我解决了。
在授权服务器中,我是这样配置的。
- 授权服务器
WebSecurityConfigurerAdapter
配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN").authorities("USER", "ADMIN");
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("123")).roles("USER");
}
...
}
下面是我的资源服务器的 /me
映射控制器
- ResourceServer
/me
映射控制器
@RestController
public class UserController {
@RequestMapping("/me")
public Principal user(Principal principal) {
return principal;
}
}
以下是我客户的 WebSecurityConfigurerAdapter
配置
- 客户端
WebSecurityConfigurerAdapter
配置
@Configuration
@EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login();
}
在客户端的控制器中,我是这样登录的。
- 在客户端控制器中记录
Principal
@GetMapping("")
public String git1() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getPrincipal());
/** Thymeleaf using this **/
Object authenticationProperty = AuthUtils.getAuthenticationProperty(auth, "principal.attributes['authorities']");
System.out.println(authenticationProperty.toString());
return VIEW_PATH + "git1";
}
结果如下
Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=USER}, {authority=ADMIN}], details={remoteAddress=127.0.0.1, sessionId=null, tokenValue=82a7a532-a31e-4d0a-bd83-f15a9cbea3bc, tokenType=Bearer, decodedDetails=null}, authenticated=true, userAuthentication={authorities=[{authority=USER}, {authority=ADMIN}], details=null, authenticated=true, principal=admin, credentials=N/A, name=admin}, oauth2Request={clientId=foo, scope=[read], requestParameters={client_id=foo}, resourceIds=[], authorities=[], approved=true, refresh=false, redirectUri=null, responseTypes=[], extensions={}, refreshTokenRequest=null, grantType=null}, clientOnly=false, principal=admin, credentials=, name=admin]
[{authority=USER}, {authority=ADMIN}]
如您所见,我在授权服务器中添加了 'ROLE_USER' 和 'ROLE_ADMIN' 权限。
在资源服务器的 Principal
对象中同时授予 'ROLE_ADMIN' 和 'ROLE_USER'。
但在客户的 Principal
对象中未授予 'ROLE_ADMIN'。只有'ROLE_USER'。
和 Principal.atttibutes['authorities']
有 'USER', 'ADMIN'.
正如@Rahil Husain 所说,有 DefaultOAuth2UserService
并且此服务授予 'ROLE_USER' 仅对 OAuth2User
对象。
首先,我通过 @Componenet
注释(@Bean
也添加了 CustomAuthoritiesExtractor
到客户端。
但这在我的项目中不起作用。
所以,我实现了 CustomOAuth2User
和 CustomOAuth2UserService
。
像这样。
CustomOAuth2User
public class CustomOAuth2User implements OAuth2User {
private List<GrantedAuthority> authorities;
private Map<String, Object> attributes;
private String name;
public CustomOAuth2User(List<GrantedAuthority> authorities, Map<String, Object> attributes) {
this.authorities = authorities;
this.attributes = attributes;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public Map<String, Object> getAttributes() {
if (this.attributes == null) {
this.attributes = new HashMap<>();
this.attributes.put("name", this.getName());
}
return attributes;
}
@Override
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
下面是CustomOAuth2UserService
CustomOAuth2UserService
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
AuthoritiesExtractor authoritiesExtractor = new CustomAuthoritiesExtractor();
List<GrantedAuthority> grantedAuthorityList = authoritiesExtractor.extractAuthorities(oAuth2User.getAttributes());
CustomOAuth2User customOAuth2User = new CustomOAuth2User(grantedAuthorityList, oAuth2User.getAttributes());
customOAuth2User.setName(oAuth2User.getName());
return customOAuth2User;
}
}
下面是我的CustomAuthoritiesExtractor
。此 class 未用作 @Bean
或 @Component
。直接在 CustomOAuth2Service
中用于映射 CustomOAuth2User
对象的权限
CustomAuthoritiesExtractor
public class CustomAuthoritiesExtractor implements AuthoritiesExtractor {
@Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(asAuthorities(map));
}
private String asAuthorities(Map<String, Object> map) {
List<String> authorities = new ArrayList<>();
List<LinkedHashMap<String, String>> authz =
(List<LinkedHashMap<String, String>>) map.get("authorities");
for (LinkedHashMap<String, String> entry : authz) {
authorities.add(entry.get("authority"));
}
return String.join(",", authorities);
}
}
最后,我将客户端的端点更改为使用我的 CustomOAuth2User
和 CustomOAuth2UserService
。
所以,我像这样更改了客户端的 WebSecurityConfigurerAdapter
配置。
@Configuration
@EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login()
/** add this config**/
.userInfoEndpoint()
.customUserType(CustomOAuth2User.class, "teemo")
.userService(this.oauth2UserService());
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return new CustomOAuth2UserService();
}
下面是我的百里香。
- 百里香叶
<div sec:authorize="isAuthenticated()">
Text visible only to authenticated users.
Authenticated username:
<div sec:authentication="name"></div>
<div sec:authorize="hasRole('USER')">hasRole('USER')</div>
<div sec:authorize="hasRole('ROLE_USER')">hasRole('ROLE_USER')</div>
<div sec:authorize="hasRole('ADMIN')">hasRole('ADMIN')</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">hasRole('ROLE_ADMIN')</div>
<!-- TRUE -->
<div sec:authorize="hasAuthority('USER')">hasAuthority('USER')</div>
<div sec:authorize="hasAuthority('ROLE_USER')">hasAuthority('ROLE_USER')</div>
<!-- TRUE -->
<div sec:authorize="hasAuthority('ADMIN')">hasAuthority('ADMIN')</div>
<div sec:authorize="hasAuthority('ROLE_ADMIN')">hasAuthority('ROLE_ADMIN')</div>
</div>
<div sec:authorize="!isAuthenticated()">Text visible only to
unauthenticated users.
</div>
结果如下
Text visible only to authenticated users. Authenticated username:
admin
hasAuthority('USER')
hasAuthority('ADMIN')
和我一样挖坑的朋友,希望对这个问题和答案有所帮助。
但我不知道这是 facto-standard 方式。
只是..现在工作。