摘要
依赖
1 2 3 4 5 6
| implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
|
WebSecurityConfig
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| package com.example.oauth2resourceserverdemo2.config;
import com.example.oauth2resourceserverdemo2.security.CustomAccessDeniedHandler; import com.example.oauth2resourceserverdemo2.security.CustomAuthExceptionEntryPoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Collection; import java.util.stream.Collectors;
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private CustomAccessDeniedHandler customAccessDeniedHandler;
@Autowired private CustomAuthExceptionEntryPoint customAuthExceptionEntryPoint;
@Override protected void configure(HttpSecurity http) throws Exception {
http.cors(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests() .antMatchers("/res/**", "/userInfo/**").authenticated() .antMatchers("/user/**").hasAnyRole("admin", "user") .antMatchers("/swagger-ui/**", "/v3/api-docs**").permitAll() .anyRequest().access("@rbacService.hasPerssion(request,authentication)");
http.oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwt -> { Collection<SimpleGrantedAuthority> authorities = ((Collection<String>) jwt.getClaims() .get("authorities")).stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet());
Collection<SimpleGrantedAuthority> scopes = ((Collection<String>) jwt.getClaims() .get("scope")).stream().map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope)) .collect(Collectors.toSet()); authorities.addAll(scopes); return new JwtAuthenticationToken(jwt, authorities); });
http.exceptionHandling() .authenticationEntryPoint(customAuthExceptionEntryPoint) .accessDeniedHandler(customAccessDeniedHandler); }
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); configSource.registerCorsConfiguration("/**", corsConfiguration);
return configSource; } }
|
WebSecurityConfig说明
整体配置方式就是spring-security的配置方式,这里唯一的不同就是如下这部分代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| http.oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwt -> { Collection<SimpleGrantedAuthority> authorities = ((Collection<String>) jwt.getClaims() .get("authorities")).stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet());
Collection<SimpleGrantedAuthority> scopes = ((Collection<String>) jwt.getClaims() .get("scope")).stream().map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope)) .collect(Collectors.toSet()); authorities.addAll(scopes); return new JwtAuthenticationToken(jwt, authorities); });
|
1
| http.oauth2ResourceServer().jwt();
|
此时就已经开启的oauth2资源服务器的支持,但是此时我们鉴权后获取到的权限是scope,这可能并不是我们希望的,所以我们可以为其指定一个转换器,从access_token的payload中获取authorities
属性,并将其设置为用户的权限,如果我们还希望同时得到scope,则也可以再次取出scope
的值放入用户的权限列表中。
配置文件
1 2 3 4 5 6 7 8 9 10 11 12
| spring: security: oauth2: resourceserver: jwt:
jwk-set-uri: http://localhost:8080/.well-known/jwks.json
|
说明
AuthServer改造
JwkSetEndpoint
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
| package com.example.oauth2authserverdemo.controller;
import com.example.oauth2authserverdemo.security.jwt.JwtTokenProperties; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpoint; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody;
import java.security.KeyPair; import java.security.interfaces.RSAPublicKey; import java.util.Map;
@FrameworkEndpoint public class JwkSetEndpoint {
@Autowired private JwtTokenProperties jwtTokenProperties;
@GetMapping("/.well-known/jwks.json") @ResponseBody public Map<String, Object> getKey() { KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(jwtTokenProperties.getJksKeyFileResource(), jwtTokenProperties.getJksStorePassword().toCharArray()); KeyPair keyPair = keyStoreKeyFactory.getKeyPair(jwtTokenProperties.getJksKeyAlias(), jwtTokenProperties.getJksKeyPassword().toCharArray()); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAKey key = new RSAKey.Builder(publicKey).build(); return new JWKSet(key).toJSONObject(); }
}
|
SecurityConfig开发对应的权限
1 2 3 4
| http.authorizeRequests() .antMatchers(customSecurityProperties.getPermitAll()).permitAll() .mvcMatchers("/.well-known/jwks.json").permitAll() .anyRequest().authenticated();
|
资源,这里以UserController举例
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 43 44 45 46 47 48 49 50 51
| package com.example.oauth2resourceserverdemo2.controller;
import com.example.oauth2resourceserverdemo2.exception.AjaxResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import java.security.Principal; import java.util.HashMap; import java.util.Map;
@RestController public class UserController {
@PreAuthorize("hasRole('admin') or hasRole('user')") @RequestMapping(value = "/user") public AjaxResponse user(Principal principal) { return AjaxResponse.success(principal); }
@PreAuthorize("hasAuthority('SCOPE_any')") @RequestMapping(value = "/user2") public AjaxResponse user2(Principal principal) { return AjaxResponse.success(principal); }
@RequestMapping("/userInfo") public Map<String, Object> userInfo(Authentication authentication){ Map<String,Object> map = new HashMap<>(); Object principal = authentication.getPrincipal(); if(principal instanceof Jwt){ map.put("username", ((Jwt) principal).getClaim("user_name")); map.putAll(((Jwt) principal).getClaims()); } return map; }
}
|
资源服务器访问方式
只支持Bearer Token
1 2 3 4 5
| http://localhost:8082/user
# 在请求的header中设置参数:参数名称:Authorization,值是`[grant_type] [access_token]`,grant_type值与access_token值之间用空格分开。例如:
bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYW55Il0sImV4cCI6MTYwNDY1ODc4NiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9hZG1pbiJdLCJqdGkiOiJjYjEzZjhmZC03NWRiLTRmODItOTkxOC00YzFjZGI3MDEwMGMiLCJjbGllbnRfaWQiOiJwb3N0bWFuIn0.W78nue0rPxB-Te7ZsxfzmTUYTasHHfQT0lMgAMG_i5g
|
使用Postman接口测试工具时,也可以使用其提供的认证功能[Authorization–>TYPE–> Bearer Token],然后将access_token填入