该篇文章主要记录,使用spring cloud security oauth2 的一些过程。
关于spring cloud security oauth2一些基本知识参考: Spring Security OAuth 2开发者指南;
编写过程过也参考过其他文章教程,例如:Spring cloud微服务实战——基于OAUTH2.0统一认证授权的微服务基础架构;Spring Boot Security OAuth2 例子(Bcrypt Encoder) ;Spring Security OAuth2实现使用JWT。
客户端安全配置
简单设置了一个默认的安全配置
package com.hq.biz.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
/**
* @author Administrator
* @Package com.hq.biz.config
* @Description: GlobalMethodSecurityConfig
* @date 2018/4/23 15:59
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfig {
}
资源配置
package com.hq.biz.config;
import com.hq.biz.handler.CustomAccessDeniedHandler;
import com.hq.biz.handler.CustomAuthEntryPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.authserver.AuthorizationServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.FileCopyUtils;
import java.io.IOException;
/**
* @author huangqi
* @Package com.hq.biz.config
* @Description: ResourceServerConfig
* @date 2018/6/27 9:39
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
Logger log = LoggerFactory.getLogger(ResourceServerConfig.class);
@Autowired
private OAuth2ClientProperties oAuth2ClientProperties;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private AuthorizationServerProperties authorizationServerProperties;
@Autowired
private CustomAuthEntryPoint customAuthEntryPoint;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Bean
public AuthorizationServerProperties authorizationServerProperties() {
return new AuthorizationServerProperties();
}
@Bean
@Qualifier("authorizationHeaderRequestMatcher")
public RequestMatcher authorizationHeaderRequestMatcher() {
return new RequestHeaderRequestMatcher("Authorization");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.exceptionHandling().authenticationEntryPoint(customAuthEntryPoint)
.and()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.requestMatcher(authorizationHeaderRequestMatcher())
.authorizeRequests()
.antMatchers("hq/login","hq/logout").permitAll();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenServices())
.accessDeniedHandler(customAccessDeniedHandler)
.authenticationEntryPoint(customAuthEntryPoint);
}
@Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("hq-jwt.cert");
String publicKey;
try {
publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
@Bean
public TokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(authorizationServerProperties.getCheckTokenAccess());
remoteTokenServices.setClientId(oAuth2ClientProperties.getClientId());
remoteTokenServices.setClientSecret(oAuth2ClientProperties.getClientSecret());
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
@Bean
public AccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
}
登录
这边我使用RestTemplate 构造token请求获取token;以及使用feign调用认证服务器端删除token方法注销登录
package com.hq.biz.controller;
import com.hq.biz.dto.UserDTO;
import com.hq.biz.entity.Result;
import com.hq.biz.enums.ResultEnum;
import com.hq.biz.feign.OAuth2ServerClient;
import com.hq.biz.feign.UserClient;
import com.hq.biz.utils.BCryptUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;
import org.springframework.http.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author Administrator
* @Package com.hq.biz
* @Description: ${TODO}(用一句话描述该文件做什么)
* @date 2018/5/4 10:26
*/
@RestController
@RequestMapping("/hq")
public class LoginController {
public static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
@Autowired
private OAuth2ClientProperties oAuth2ClientProperties;
@Autowired
private OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails;
@Autowired
private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
@Autowired
private OAuth2ServerClient oAuth2ServerClient;
@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;
/**
* 通过密码授权方式向授权服务器获取令牌
*
* @return
* @throws Exception
*/
@PostMapping(value = "/login")
public Result login(@RequestParam("userName") String userName, @RequestParam("passWord") String passWord) throws Exception {
//验证用户名密码
Result userDTOResult = userClient.queryByName(userName);
if (!ResultEnum.SUCCESS.getCode().equals(userDTOResult.getRespCode())) {
return new Result(ResultEnum.LOGIN_FAIL);
}
UserDTO respData = userDTOResult.getRespData();
if(BCryptUtil.isMatch(passWord,respData.getPassWord())){
ResponseEntity responseEntity = getToken(userName, passWord);
if (HttpStatus.OK.equals(responseEntity.getStatusCode())) {
DefaultOAuth2AccessToken body = (DefaultOAuth2AccessToken) responseEntity.getBody();
Map tkMap = new HashMap<>(5);
tkMap.put("access_token", body.getValue());
tkMap.put("refresh_token", body.getRefreshToken().getValue());
return Result.returnOk(tkMap);
} else {
return new Result(ResultEnum.LOGIN_FAIL);
}
}else {
return new Result(ResultEnum.LOGIN_USER_ERR);
}
}
@RequestMapping("/logout")
public Result exit(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
String tokenValue = authHeader.replace("bearer", "").trim();
Result result = oAuth2ServerClient.removeToken(tokenValue);
if (!ResultEnum.SUCCESS.getCode().equals(result.getRespCode())) {
return new Result(ResultEnum.LOGOUT_FAIL);
}
return new Result(ResultEnum.SUCCESS.getCode(), "注销成功");
}
@PostMapping(value = "/hello")
public Result hello() throws Exception {
return Result.returnOk("hello");
}
public ResponseEntity getToken(String userName, String passWord) {
//Http Basic 验证
String clientAndSecret = oAuth2ClientProperties.getClientId() + ":" + oAuth2ClientProperties.getClientSecret();
//这里需要注意为 Basic 而非 Bearer
clientAndSecret = "Basic " + Base64.getEncoder().encodeToString(clientAndSecret.getBytes());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Authorization", clientAndSecret);
//授权请求信息
MultiValueMap map = new LinkedMultiValueMap<>();
map.put("username", Collections.singletonList(userName));
map.put("password", Collections.singletonList(passWord));
map.put("grant_type", Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType()));
map.put("scope", oAuth2ProtectedResourceDetails.getScope());
//HttpEntity
HttpEntity httpEntity = new HttpEntity(map, httpHeaders);
//获取 Token
ResponseEntity exchange = restTemplate.exchange(oAuth2ProtectedResourceDetails.getAccessTokenUri(), HttpMethod.POST, httpEntity, OAuth2AccessToken.class);
return exchange;
}
}
总配置
package com.hq.biz;
import com.hq.biz.filter.PreRequestZuulFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
@EnableFeignClients
public class CloudGatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(CloudGatewayServerApplication.class, args);
}
@Bean
public PreRequestZuulFilter preRequestZuulFilter(){
return new PreRequestZuulFilter();
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
配置文件
server:
port: 9999
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka/
logging:
level:
com.hq.*: debug
spring:
application:
name: biz-server
zuul:
routes:
api-a:
path: /api-a/**
serviceId: cloud-user
security:
oauth2:
client:
clientId: hq
clientSecret: hq
userAuthorizationUri: http://localhost:8000/oauth/authorize
grant-type: password
scope: xx
access-token-uri: http://localhost:8000/oauth/token
resource:
userInfoUri: http://localhost:8000/user
authorization:
check-token-access: http://localhost:8000/oauth/check_token
feign:
hystrix:
enabled: true