接着上一篇:SpringBoot2 整合OAuth2实现统一认证
上一篇整合介绍了OAuth2的认证服务,接下来利用认证服务提供的token来包含我们的资源。
环境:2.2.11.RELEASE + OAuth2 + Redis
- pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.2.11.RELEASE</version> </dependency>
- application.yml
server: port: 8088 --- spring: application: name: oauth-resource --- spring: redis: host: localhost port: 6379 password: database: 1 lettuce: pool: maxActive: 8 maxIdle: 100 minIdle: 10 maxWait: -1
- Domain对象(我们在认证服务上是把Users对象序列化存储到了Redis,所以这里还需要这个类,其实如果用了网关,这些认证就不需要在资源端进行了)
public class Users implements UserDetails, Serializable { private static final long serialVersionUID = 1L; private String id ; private String username ; private String password ; }
- 核心配置类
@Configuration @EnableResourceServer public class OAuthConfig extends ResourceServerConfigurerAdapter { private static final Logger logger = LoggerFactory.getLogger(OAuthConfig.class) ; public static final String RESOURCE_ID = "gx_resource_id"; @Resource private RedisConnectionFactory redisConnectionFactory ; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId(RESOURCE_ID) ; OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator()); resources.authenticationEntryPoint(oAuth2AuthenticationEntryPoint) ; resources.tokenExtractor((request) -> { String tokenValue = extractToken(request) ; if (tokenValue != null) { PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, ""); return authentication; } return null; }) ; } private String extractToken(HttpServletRequest request) { // first check the header... Authorization: Bearer xxx String token = extractHeaderToken(request); // sencod check the header... access_token: xxx if (token == null) { token = request.getHeader("access_token") ; } // bearer type allows a request parameter as well if (token == null) { logger.debug("Token not found in headers. Trying request parameters.") ; token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN) ; if (token == null) { logger.debug("Token not found in request parameters. Not an OAuth2 request.") ; } else { request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE); } } return token; } private String extractHeaderToken(HttpServletRequest request) { Enumeration<String> headers = request.getHeaders("Authorization"); while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that) String value = headers.nextElement(); if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) { String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim(); // Add this here for the auth details later. Would be better to change the signature of this method. request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim()); int commaIndex = authHeaderValue.indexOf(','); if (commaIndex > 0) { authHeaderValue = authHeaderValue.substring(0, commaIndex); } return authHeaderValue; } } return null; } @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() ; http.requestMatcher(request -> { String path = request.getServletPath() ; if (path != null && path.startsWith("/demo")) { return true ; } return false ; }) .authorizeRequests() .anyRequest() .authenticated() ; } @Bean public TokenStore tokenStore() { TokenStore tokenStore = null ; tokenStore = new RedisTokenStore(redisConnectionFactory) ; return tokenStore ; } @Bean public WebResponseExceptionTranslator<?> webResponseExceptionTranslator() { return new DefaultWebResponseExceptionTranslator() { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public ResponseEntity translate(Exception e) throws Exception { ResponseEntity<OAuth2Exception> responseEntity = super.translate(e) ; ResponseEntity<Map<String, Object>> customEntity = exceptionProcess(responseEntity); return customEntity ; } }; } private static ResponseEntity<Map<String, Object>> exceptionProcess( ResponseEntity<OAuth2Exception> responseEntity) { Map<String, Object> body = new HashMap<>() ; body.put("code", -1) ; OAuth2Exception excep = responseEntity.getBody() ; String errorMessage = excep.getMessage(); if (errorMessage != null) { errorMessage = "认证失败,非法用户" ; body.put("message", errorMessage) ; } else { String error = excep.getOAuth2ErrorCode(); if (error != null) { body.put("message", error) ; } else { body.put("message", "认证服务异常,未知错误") ; } } body.put("data", null) ; ResponseEntity<Map<String, Object>> customEntity = new ResponseEntity<>(body, responseEntity.getHeaders(), responseEntity.getStatusCode()) ; return customEntity; } }
核心配置类主要完整,开启资源服务认证,定义我们需要保护的接口,token的存储对象及错误信息的统一处理。
- 测试接口
@RestController @RequestMapping("/demo") public class DemoController { @GetMapping("/res") public Object res() { return "success" ; } }
测试:
先直接访问token或者传一个错误的tokenj
接下先获取一个正确的token
成功了
完毕!!!