該篇文章主要記錄,使用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。html
客戶端安全配置
簡單設置了一個默認的安全配置
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方法註銷登陸java
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<UserDTO> 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<OAuth2AccessToken> responseEntity = getToken(userName, passWord); if (HttpStatus.OK.equals(responseEntity.getStatusCode())) { DefaultOAuth2AccessToken body = (DefaultOAuth2AccessToken) responseEntity.getBody(); Map<String, String> 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<OAuth2AccessToken> 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<String, String> 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<OAuth2AccessToken> 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