SpringBoot 整合 OAuth2 實(shí)現(xiàn)資源保護(hù)
作者:Springboot實(shí)戰(zhàn)案例錦集
我們?cè)谡J(rèn)證服務(wù)上是把Users對(duì)象序列化存儲(chǔ)到了Redis,所以這里還需要這個(gè)類,其實(shí)如果用了網(wǎng)關(guān),這些認(rèn)證就不需要在資源端進(jìn)行了,核心配置類主要完整,開(kāi)啟資源服務(wù)認(rèn)證,定義我們需要保護(hù)的接口,token的存儲(chǔ)對(duì)象及錯(cuò)誤信息的統(tǒng)一處理。
上一篇整合介紹了OAuth2的認(rèn)證服務(wù),接下來(lái)利用認(rèn)證服務(wù)提供的token來(lái)包含我們的資源。
環(huán)境: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對(duì)象(我們?cè)谡J(rèn)證服務(wù)上是把Users對(duì)象序列化存儲(chǔ)到了Redis,所以這里還需要這個(gè)類,其實(shí)如果用了網(wǎng)關(guān),這些認(rèn)證就不需要在資源端進(jìn)行了)
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 = "認(rèn)證失敗,非法用戶" ;
body.put("message", errorMessage) ;
} else {
String error = excep.getOAuth2ErrorCode();
if (error != null) {
body.put("message", error) ;
} else {
body.put("message", "認(rèn)證服務(wù)異常,未知錯(cuò)誤") ;
}
}
body.put("data", null) ;
ResponseEntity<Map<String, Object>> customEntity = new ResponseEntity<>(body,
responseEntity.getHeaders(), responseEntity.getStatusCode()) ;
return customEntity;
}
}
核心配置類主要完整,開(kāi)啟資源服務(wù)認(rèn)證,定義我們需要保護(hù)的接口,token的存儲(chǔ)對(duì)象及錯(cuò)誤信息的統(tǒng)一處理。
- 測(cè)試接口
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/res")
public Object res() {
return "success" ;
}
}
測(cè)試:
先直接訪問(wèn)token或者傳一個(gè)錯(cuò)誤的tokenj
圖片
接下先獲取一個(gè)正確的token
圖片
圖片
責(zé)任編輯:武曉燕
來(lái)源:
Spring全家桶實(shí)戰(zhàn)案例源碼