最近有个新项目需要我搭建一下,需要对接第三方用户系统,对方使用的是
keycloak
认证中心平台,所以只需要拿到对方的/certs地址就可以进行对用户的请求头Authorization
的token
进行签名的校验,然后获取token
中的权限和一些信息内容
1.pom.xml中引入spring-security依赖
<!--security start-->
<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>
<!--security end-->
因为目前只做鉴权用户和识别用户权限,只需要这两个就够了
2.配置application.yml中的授权地址
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://project.com/auth/realms/realms-name/protocol/openid-connect/certs
基本认证中心的地址都长的差不多
3.配置SecurityConfig
@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用来测试可行性
@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
加一个允许的权限,不然Security
会进行权限拦截
这样:
@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
@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()
//先校验客户端权限,接口校验用户权限,因为hasAnyAuthority不能实现hasEveryAuthority,需要分开校验
.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
类
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_
来区分客户端和用户的权限
在方法上添加注解
@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')")
,因为不能同时校验两个权限,目前想到的方式就是分开校验。