上一篇整合介绍了OAuth2的认证服务,接下来利用认证服务提供的token来包含我们的资源。
环境:2.4.12 + 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
图片
图片