这两天负责一个新项目的搭建,需要对接第三方用户系统,对方使用的是keycloak
认证中心平台,所以只需要拿到对方的/certs地址就可以进行对用户的请求头Authorization
的token
进行签名的校验,然后获取token
中的权限和一些信息内容
1.pom.xml中引入spring-security依赖
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
|
因为目前只做鉴权用户和识别用户权限,只需要这两个就够了
2.配置application.yml中的授权地址
1 2 3 4 5 6
| spring: security: oauth2: resourceserver: jwt: jwk-set-uri: http://project.com/auth/realms/realms-name/protocol/openid-connect/certs
|
基本认证中心的地址都长的差不多
3.配置SecurityConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.sessionManagement().sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy()) .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().authorizeRequests() .antMatchers("/api/v1/sample/messages").permitAll() .antMatchers("/api/v1/sample/**").hasAnyAuthority("SCOPE_ligafi.end","ligafi.end") .anyRequest().authenticated() .and() .oauth2ResourceServer().authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .jwt(); } }
|
4.写一个SampleController用来测试可行性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @RestController @RequestMapping("/api/v1/sample") @Api(value = "A Sample Controller", tags = {"Demo Tag"}) public class SampleController extends BaseController{ @PostMapping("/messages") @ApiOperation("from user get message") public String message(){ return "this is a test message"; } @GetMapping("/getParam") @ApiOperation("getParam") public String getParam() { return "this is a test getParam"; }
}
|
这里的Swagger
引入和配置就不说了,有手就行的东西,无非就是在SecurityConfig
加一个允许的权限
这样:
1 2 3 4 5 6 7
| @Override public void configure(WebSecurity web) { web.ignoring().antMatchers("/v2/api-docs/**") .antMatchers("/swagger-ui.html") .antMatchers("/swagger-resources/**") .antMatchers("/webjars/**"); }
|
5.找到keycloak
平台获取token
的地址,获取一个token进行测试
https://project.com/auth/realms/realms-name/protocol/openid-connect/token
该平台因为需要校验用户的权限ROLE
和客户端的权限SCOPE
,这些JWT(Json Web Token)
里面的内容就不做解释了,去https://jwt.io/就可以了解到了

从图中可以看到,获取到了token
,尝试解析一下token

现在第三方调用我们业务的时候需要同时校验realm_access
中的roles
里面的ligafi.end
权限和客户端的scope
权限ligafi.end
,现在去Swagger
试试

需要注意的是,请求头的Authorization
是Bearer
类型的token

看着好像是通过了,但是我们通过在源码里面打断点看看情况
org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyAuthorityName

获取到的权限里面只有客户端的scope
部分,而且hasAnyAuthority("SCOPE_ligafi.end","ligafi.end")
的校验中,只要有一项权限符合要求就通过,所以不能同时校验客户端scope
和用户的roles
先处理无法获取用户权限的问题:
在配置SecurityConfig
中自定义一个AuthenticationConverter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.sessionManagement().sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy()) .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().authorizeRequests() .antMatchers("/api/v1/sample/messages").permitAll() .antMatchers("/api/v1/sample/**").hasAuthority("SCOPE_ligafi.end") .anyRequest().authenticated() .and() .oauth2ResourceServer().authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .jwt() .jwtAuthenticationConverter(grantedAuthoritiesExtractorConverter()); }
@Bean Converter<Jwt, AbstractAuthenticationToken> grantedAuthoritiesExtractorConverter() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesExtractor()); return jwtAuthenticationConverter; }
@Bean GrantedAuthoritiesExtractor grantedAuthoritiesExtractor() { return new GrantedAuthoritiesExtractor(); }
@Override public void configure(WebSecurity web) { web.ignoring().antMatchers("/v2/api-docs/**") .antMatchers("/swagger-ui.html") .antMatchers("/swagger-resources/**") .antMatchers("/webjars/**"); }
}
|
通过@EnableGlobalMethodSecurity(prePostEnabled = true)
开启方法上的注解@PreAuthorize
来校验权限
GrantedAuthoritiesExtractor
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class GrantedAuthoritiesExtractor implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override public Collection<GrantedAuthority> convert(Jwt jwt) { List<GrantedAuthority> authorities = new ArrayList<>(); String realmAccess = "realm_access"; String roles = "roles"; String scope = "scope"; if (jwt.containsClaim(realmAccess)) { JSONObject realmAccessJson = (JSONObject) jwt.getClaims().get(realmAccess); if (realmAccessJson.containsKey(roles)) { JSONArray realmRoles = (JSONArray) realmAccessJson.get(roles); for (Object realmRole : realmRoles) { authorities.add(new SimpleGrantedAuthority("ROLE_" +realmRole.toString())); } } } if (jwt.containsClaim(scope)) { String scopeStr = (String) jwt.getClaims().get(scope); if (!StringUtils.isEmpty(scopeStr) && !scopeStr.isEmpty()) { String[] scopes = scopeStr.split("\\s"); for (String scopeAuthority : scopes) { authorities.add(new SimpleGrantedAuthority("SCOPE_" + scopeAuthority)); } } } return authorities; } }
|
用ROLE_
和SCOPE_
来区分客户端和用户的权限
在方法上添加注解
1 2 3 4 5 6
| @GetMapping("/getParam") @PreAuthorize("hasRole('ligafi.end')") @ApiOperation("getParam") public String getParam() { return "this is a test getParam"; }
|
hasRole
会自动帮权限加上ROLE_
,所以我们之前就直接authorities.add(new SimpleGrantedAuthority("ROLE_" +realmRole.toString()))
自己手动加上。
再去Swagger
试试


现在所有权限都获取到了,而且校验了两次,所以这样校验是正确的方式

成功返回结果。
总结:最主要的地方就是SecurityConfig
中自定义的jwtAuthenticationConverter
和方法上的注解@PreAuthorize("hasRole('ligafi.end')")
,因为不能同时校验两个权限,目前想到的方式就是分开校验。