一、简介
OAuth2是一套开放标准,Spring Cloud Security Oauth2是对它的一种实现。
1.1 角色定义
1.1.1 客户端
本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,如:移动端、pc端等。
1.1.2 资源拥有者
通常为用户、也可为应用程序,即资源的拥有者。
1.1.3 资源服务器
存储资源的服务器,一般为提供业务接口服务的程序。
1.1.4 授权服务器
用于对资源拥有者的身份进行认证、对访问资源进行授权,认证成功发放令牌(access_token)给客户端,作为客户端访问资源服务器的凭证;授权服务器就是对客户端和资源拥有着进行认证授权,使其可以访问资源服务器获取资源。
1.2 四种模式
1.2.1 授权码模式
授权码模式(authorization code)是功能最完整、流程最严密的授权模式,code保证了token的安全性,即使code被拦截,由于没有app_secret,也是无法通过code获得token的。
1.2.2 隐式授权模式/简化模式
和授权码模式类似,少了获取code的步骤,是直接获取令牌token的,适用于公开的浏览器单页应用,令牌直接从授权服务器返回,不支持刷新令牌,且没有code安全保证,令牌容易因为被拦截窃听而泄露。
1.2.3 密码模式
使用用户名/密码作为授权方式从授权服务器上获取令牌,一般不支持刷新令牌。
1.2.4 客户端凭证模式
一般用于资源服务器是应用的一个后端模块,客户端向认证服务器验证身份来获取令牌。
二、授权服务搭建
2.1 maven依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!-- 此依赖包含security,所以不需要额外导入security --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!-- 健康检查 oauth2会使用到一些端点信息需要加上此依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- 使用redis存储token才需要引入,其他方式存储可以不配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
|
2.2 配置类
2.2.1 鉴权服务配置
配置类添加@EnableAuthorizationServer,继承org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter类
需重写以下三个方法
客户端详情信息服务配置,能够使用内存、jdbc方式来实现clientDetailsService(客户端详情服务),负责查找clientDetails。clientDetails能够在应用程序运行时进行更新,通过访问存储服务(如jdbcClientDetailsService)或自己实现ClientRegistrationService接口进行管理
关于clientDetails的几个属性如下:
- clientId:用来表示客户的id
- secret:用来限制客户端的访问访问,如果为空(默认为空)的话,客户端拥有全部的访问范围
- authorizedGrantType:客户端可以使用的授权类型,默认为空(client_credentials,password,authorization_code,implicit,refresh_token)
- authorities:客户端可以使用的权限
令牌访问端点,用来配置令牌的访问端点和令牌服务
令牌访问端点安全策略,用来配置令牌端点的安全约束
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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| @EnableAuthorizationServer @Configuration public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private AuthorizationCodeServices authorizationCodeServices; @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenStore tokenStore; @Autowired private ClientDetailsService clientDetailsService; @Autowired private JwtAccessTokenConverter accessTokenConverter; @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserDetailsService userDetailsService;
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService); }
@Bean public AuthorizationServerTokenServices tokenService() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setClientDetailsService(clientDetailsService); defaultTokenServices.setSupportRefreshToken(true); defaultTokenServices.setTokenStore(tokenStore); TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); defaultTokenServices.setTokenEnhancer(tokenEnhancerChain); defaultTokenServices.setAccessTokenValiditySeconds(7200); defaultTokenServices.setRefreshTokenValiditySeconds(259200); return defaultTokenServices; }
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints .authenticationManager(authenticationManager) .authorizationCodeServices(authorizationCodeServices) .tokenServices(tokenService()) .userDetailsService(userDetailsService) .allowedTokenEndpointRequestMethods(HttpMethod.POST); }
@Override public void configure(AuthorizationServerSecurityConfigurer security) { security .tokenKeyAccess("permitAll()") .checkTokenAccess("permitAll()") .allowFormAuthenticationForClients(); }
@Bean public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) { return new JdbcAuthorizationCodeServices(dataSource); }
@Bean public ClientDetailsService clientDetailsService(DataSource dataSource) { JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); clientDetailsService.setPasswordEncoder(passwordEncoder); return clientDetailsService; } }
|
2.2.2 令牌存储策略配置
这里编写了三种令牌生成后的存储方式,根据自己的需求进行修改,后面的配置以jwt策略为准,在AuthorizationServerConfiguration类中的tokenService()进行令牌注入配置
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
|
@Configuration public class TokenConfig {
@Autowired private RedisConnectionFactory redisConnectionFactory;
private String SIGNING_KEY = "uaa123";
@Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); }
@Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(SIGNING_KEY); return converter; }
}
|
2.2.3 security配置
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
| @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService;
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .and().csrf().disable() .authorizeRequests().anyRequest().authenticated(); }
@Override public void configure(WebSecurity web) throws Exception { super.configure(web); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
@Bean public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); } }
|
2.2.4 四种模式的认证请求
2.2.4.1 授权码模式(较安全模式):
客户端进行授权请求(此处授权的客户端信息为oauth2表格维护的客户端信息),用户登录后进行登录(此处登录使用的是userDetailService查回的信息,即security管理的用户)后进行授权,通过回调url返回code(授权码)https://www.baidu.com/?code=m4aOtl
根据授权码和客户端secret等信息获取token(此处的token使用的是jwt):
1 2 3 4 5 6 7 8
| { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicjEiXSwidXNlcl9uYW1lIjoiemhhbmdzYW4iLCJzY29wZSI6WyJST0xFX0FQSSJdLCJleHAiOjE2MDY2MjY0NTcsImF1dGhvcml0aWVzIjpbInAxIiwicDMiXSwianRpIjoiMDQ1N2ZiNTgtMmY0Ny00NGJmLTk0NzMtMTRiNWU2ZmI4M2ZmIiwiY2xpZW50X2lkIjoiYzEifQ.EjYisjGOtqrTJQc7nqWBtaStphF1PxTe07_pC0oireM", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicjEiXSwidXNlcl9uYW1lIjoiemhhbmdzYW4iLCJzY29wZSI6WyJST0xFX0FQSSJdLCJhdGkiOiIwNDU3ZmI1OC0yZjQ3LTQ0YmYtOTQ3My0xNGI1ZTZmYjgzZmYiLCJleHAiOjE2MDY4Nzg0NTcsImF1dGhvcml0aWVzIjpbInAxIiwicDMiXSwianRpIjoiNGQ4NjMxM2YtYjI3Zi00NTZiLWJlYzItMjc1NDY2N2UwMjIwIiwiY2xpZW50X2lkIjoiYzEifQ.1qItJNh8cGEiTEGJY1b2gBeeGq904lx8sZB7GaRMPmk", "expires_in": 7199, "scope": "ROLE_API", "jti": "0457fb58-2f47-44bf-9473-14b5e6fb83ff" }
|
2.2.4.2 隐式授权模式/简化模式
http://localhost:9305/oauth/authorize?client_id=c1&response_type=token&scope=ROLE_API&redirect_uri=http://www.baidu.com
此模式不需要获取授权码,用户登录后进行登录(此处登录使用的是userDetailService查回的信息,即security管理的用户)后进行授权,回调url返回token
https://www.baidu.com/#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicjEiXSwidXNlcl9uYW1lIjoiemhhbmdzYW4iLCJzY29wZSI6WyJST0xFX0FQSSJdLCJleHAiOjE2MDY2Mjc3NzUsImF1dGhvcml0aWVzIjpbInAxIiwicDMiXSwianRpIjoiNjhiOTkyMjYtNTI3NC00MmI5LTk1MTMtNzA5NzE2OWE2OTA2IiwiY2xpZW50X2lkIjoiYzEifQ.3jVUpS6o_zmRRF0eHjjCRHHwYncbaaZg4zIBt-0Jz_s&token_type=bearer&expires_in=7199&jti=68b99226-5274-42b9-9513-7097169a6906
2.2.4.3 密码模式(一般用于自有开发的客户端使用,否则有密码泄露风险)
http://localhost:9300/auth/oauth/token?username=zhangsan&password=123&grant_type=password&scope=ROLE_API&client_id=c1&client_secret=secret
1 2 3 4 5 6 7 8
| { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicjEiXSwidXNlcl9uYW1lIjoiemhhbmdzYW4iLCJzY29wZSI6WyJST0xFX0FQSSJdLCJleHAiOjE2MDY2Mjg0MjYsImF1dGhvcml0aWVzIjpbInAxIiwicDMiXSwianRpIjoiNDRjZjNlN2QtNjIwYi00M2FhLThjMWItMGZkMWVkZjE3YTMwIiwiY2xpZW50X2lkIjoiYzEifQ.N_Wd2DKyEpzGBmnvnDrP-vx4lSzIjiInQRNTsJtxMkM", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicjEiXSwidXNlcl9uYW1lIjoiemhhbmdzYW4iLCJzY29wZSI6WyJST0xFX0FQSSJdLCJhdGkiOiI0NGNmM2U3ZC02MjBiLTQzYWEtOGMxYi0wZmQxZWRmMTdhMzAiLCJleHAiOjE2MDY4ODA0MjYsImF1dGhvcml0aWVzIjpbInAxIiwicDMiXSwianRpIjoiNmJiOWQ5YTAtYzhiYi00ZDk4LTgwMGYtMzY1NGY5ZjU1MDgxIiwiY2xpZW50X2lkIjoiYzEifQ.AL2ODdEhSKwuzrkYCvikY6UBrljFBroPtFiI9UT5xs4", "expires_in": 7199, "scope": "ROLE_API", "jti": "44cf3e7d-620b-43aa-8c1b-0fd1edf17a30" }
|
2.2.4.4 客户端模式(这种方式最简便也最不安全,需要对客户端完全信任,用于合作系统的对接,没有刷新token)
http://localhost:9305/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials
1 2 3 4 5 6 7
| { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicjEiXSwic2NvcGUiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiIsIlJPTEVfQVBJIl0sImV4cCI6MTYwNjYyODU2MSwianRpIjoiZDBmODBjNTEtMWU5Ny00ODA5LThjNmEtYmEzYjYzNTZhMGE5IiwiY2xpZW50X2lkIjoiYzEifQ.I7KIvzQLYqlkzMQqw2hOPZ-ev8cV4ehoDU9LsP4qAAM", "token_type": "bearer", "expires_in": 7199, "scope": "ROLE_ADMIN ROLE_USER ROLE_API", "jti": "d0f80c51-1e97-4809-8c6a-ba3b6356a0a9" }
|
三、资源服务(学习过渡,之后的资源服务不这么配置,参考目录五)
3.1 maven依赖(同鉴权服务)
3.2 配置类
3.2.1 资源服务配置
对于token的校验可以请求远程auth服务,也可以使用其他tokenStore进行自我校验,本次使用jwt方式进行自我校验,不请求远程资源服务器
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
|
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "r1";
@Autowired private TokenStore tokenStore; @Autowired private ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; @Autowired private MyAccessDeniedHandler myAccessDeniedHandler;
@Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/**").access("#oauth2.hasScope('ROLE_API')") .and() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); }
@Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId(RESOURCE_ID)
.tokenStore(tokenStore) .stateless(true) .accessDeniedHandler(myAccessDeniedHandler) .authenticationEntryPoint(resourceAuthExceptionEntryPoint); }
@Bean public ResourceServerTokenServices tokenService(){ RemoteTokenServices services = new RemoteTokenServices(); services.setCheckTokenEndpointUrl("http://localhost:9305/oauth/check_token"); services.setClientId("c1"); services.setClientSecret("secret"); return services; } }
|
3.2.2 令牌存储策略(同鉴权服务2.2.2)
3.2.3 security配置
将方法权限的控制交给security
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/order/r1").hasAuthority("p2") .antMatchers("order/r2").hasAuthority("p2") .antMatchers("/order/**").authenticated() .anyRequest().permitAll(); } }
|
四、网关资源校验服务
网关进行token校验,合法后对用户和权限信息封装,再下发下游服务,下游服务可以不再集成oauth2依赖,获取网关封装的权限信息,再次封装入security的上下文中,通过security完成权限校验
- 网关对鉴权服务的认证请求放行
- 网关的资源服务会对用户的请求进行合法性校验
- 网关过滤器对token进行封装自定义用户信息json格式下发下游服务
- 下游服务过滤器对自定义用户权限信息封装的security安全上下文中
- security接管下游服务的权限控制
4.1 maven依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <!-- 网关依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!-- 此依赖包含security,所以不需要额外导入security --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!-- 健康检查 oauth2会使用到一些端点信息需要加上此依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- 使用redis存储token才需要引入,其他方式存储可以不配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
|
4.2 配置类
4.2.1 资源服务配置
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
|
@Configuration public class ResourceServerConfig {
public static final String RESOURCE_ID = "r1"; @Autowired private TokenStore tokenStore; @Autowired private ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; @Autowired private MyAccessDeniedHandler myAccessDeniedHandler;
@Configuration @EnableResourceServer public class OrderServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .tokenStore(tokenStore) .resourceId(RESOURCE_ID) .stateless(true) .accessDeniedHandler(myAccessDeniedHandler) .authenticationEntryPoint(resourceAuthExceptionEntryPoint); }
@Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/auth/**").permitAll();
} }
@Configuration @EnableResourceServer public class PayServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .tokenStore(tokenStore) .resourceId(RESOURCE_ID) .stateless(true); }
@Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/api/**").access("#oauth2.hasScope('ROLE_API')"); } } }
|
4.2.2 令牌存储策略配置(同鉴权服务2.2.2)
4.2.3 security配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/**").permitAll() .and() .csrf() .disable(); } }
|
4.2.4 过滤器封装token用户及权限信息
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
|
@Component public class AuthFilter extends ZuulFilter { @Override public String filterType() { return "pre"; }
@Override public int filterOrder() { return 0; }
@Override public boolean shouldFilter() { return true; }
@Override public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication instanceof OAuth2Authentication)) { return null; } OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication; Authentication userAuthentication = oAuth2Authentication.getUserAuthentication(); Object principal = userAuthentication.getPrincipal();
List<String> authList= new ArrayList<>(); userAuthentication.getAuthorities().forEach(auth-> authList.add(auth.getAuthority()));
OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request(); Map<String, String> requestParameters = oAuth2Request.getRequestParameters(); Map<String,Object> jsonToken = new HashMap<>(requestParameters); jsonToken.put("principal",principal); jsonToken.put("authorities",authList);
ObjectMapper objectMapper = new ObjectMapper(); String jsonTokenStr = ""; try { jsonTokenStr = objectMapper.writeValueAsString(jsonToken); } catch (JsonProcessingException e) { e.printStackTrace(); }
currentContext.addZuulRequestHeader("json-token", Base64.getEncoder().encodeToString(jsonTokenStr.getBytes(StandardCharsets.UTF_8)));
return null; } }
|
4.2.5 自定义处理类
4.2.5.1 权限不足处理类
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
|
@Slf4j @Component @AllArgsConstructor public class MyAccessDeniedHandler extends OAuth2AccessDeniedHandler { private final ObjectMapper objectMapper;
@Override @SneakyThrows public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException) { log.info("授权失败,禁止访问 {}", request.getRequestURI()); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpStatus.FORBIDDEN.value()); PrintWriter printWriter = response.getWriter(); printWriter.append(objectMapper.writeValueAsString(new Result(false,403,"权限不足,禁止访问"))); } }
|
4.2.5.2 各种AuthenticationException细化处理
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
|
@Slf4j @Component @AllArgsConstructor public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint { private final ObjectMapper objectMapper; private static final Logger logger = LoggerFactory.getLogger(ResourceAuthExceptionEntryPoint.class); @Override @SneakyThrows public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); Result result = new Result(); result.setCode(HttpStatus.UNAUTHORIZED.value()); if (authException != null) { result.setMsg("error"); result.setData(authException.getMessage()); }
if(authException.getCause() == null){ result.setMsg("error"); logger.error("no right to access or token invalid!,msg:{}",authException.getMessage()); } else{ String exceptionStr = authException.getCause().toString(); if (StringUtils.contains(exceptionStr, "unauthorized")) { result.setCode(11); result.setMsg(BusinessErrorEnum.LOGIN_USER_NOTEXIST.getText()); logger.info(BusinessErrorEnum.LOGIN_USER_NOTEXIST.getText(),authException);
} else if (StringUtils.contains(exceptionStr, "invalid_grant")) { result.setCode(BusinessErrorEnum.LOGIN_PASSWORD_ERROR.getIndex()); result.setMsg(BusinessErrorEnum.LOGIN_PASSWORD_ERROR.getText()); logger.info(BusinessErrorEnum.LOGIN_PASSWORD_ERROR.getText(),authException);
} else if (StringUtils.contains(exceptionStr, "invalid_token")) { result.setCode(BusinessErrorEnum.LOGIN_TOKEN_ERROR.getIndex()); result.setMsg(BusinessErrorEnum.LOGIN_TOKEN_ERROR.getText()); logger.info(BusinessErrorEnum.LOGIN_TOKEN_ERROR.getText(),authException);
} else { result.setCode(BusinessErrorEnum.LOGIN_ERROR.getIndex());
result.setMsg(BusinessErrorEnum.LOGIN_ERROR.getText()); logger.info(BusinessErrorEnum.LOGIN_ERROR.getText(),authException); } }
response.setStatus(HttpStatus.UNAUTHORIZED.value()); PrintWriter printWriter = response.getWriter(); printWriter.append(objectMapper.writeValueAsString(result));
} }
|
五、下游资源微服务
5.1 maven依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency>
|
5.2 配置
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
| @Configuration @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenAuthenticationFilter tokenAuthenticationFilter;
@Autowired private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/pay/r1").hasAuthority("p3") .antMatchers("pay/r2").hasAuthority("p1") .antMatchers("/pay/**").authenticated() .anyRequest().permitAll(); http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint).accessDeniedHandler(myAccessDeniedHandler); } }
|
5.3 自定义异常处理
5.3.1 权限不足
1 2 3 4 5 6 7 8 9 10 11 12
|
@Component @Slf4j public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { Result result = new Result(false,403,"权限不足"); HttpResponseUtil.responseJsonWriter(httpServletResponse,result); } }
|
5.3.2 匿名用户无权限处理
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component @Slf4j public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
Result result = new Result(false,403,"超过登录有效期,请重新登录!"); HttpResponseUtil.responseJsonWriter(response, result); } }
|
5.4 封装自定义权限到security安全上下文
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
|
@Component public class TokenAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader("json-token"); if(StringUtils.hasText(token)){ String jsonTokenStr = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8); ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode = objectMapper.readTree(jsonTokenStr); String principal = jsonNode.get("principal").asText(); UserPo userPo = new UserPo(); userPo.setUsername(principal); Iterator<JsonNode> authorities = jsonNode.get("authorities").iterator(); List<String> authList = new ArrayList<>(); while (authorities.hasNext()){ authList.add(authorities.next().asText()); } String[] authArr = authList.toArray(new String[authList.size()]);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPo,null, AuthorityUtils.createAuthorityList(authArr)); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authenticationToken); } filterChain.doFilter(httpServletRequest,httpServletResponse); } }
|
5.5 自定义security权限注解校验
5.5.1 处理类
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
|
@Component("pms") public class PermissionUtil {
public boolean hasPermission(String permission) {
if(StringUtils.isEmpty(permission)){ return false; }
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { return false; } UserPo userPo = (UserPo) authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); return authorities.stream() .map(GrantedAuthority::getAuthority) .filter(StringUtils::hasText) .anyMatch(x -> PatternMatchUtils.simpleMatch(permission, x)); } }
|
5.5.2 注解
方法上添加注解进行权限控制
1
| @PreAuthorize("@pms.hasPermission('p1')" )
|
六、数据库表格
https://www.cnblogs.com/zxy-come-on/p/14047791.html