Spring Cloud Gateway 整合 Spring Security Oauth2
最近一直在搞Spring Cloud Gateway 整合 Spring Security Oauth2,網上找了很多資料,搞了好幾天,今天終於搞完了,先來說一下具體思路吧。(里面只展示一些主要的代碼)
服務名 | 注釋 | 描述 |
---|---|---|
zswyAuth-8850 | 認證服務器 | 實現一個簡單的基本的 oauth2認證服務 使用 jwt token,使用自定義 JwtTokenStore |
zswyGateway-9527 | 網關 | 實現簡單的鑒權服務,調用zswyAuth-8850的auth/check_token進行token校檢、鑒權,最后通過gateway進行調用 |
zswyBlog | 資源服務器 | 使用 spring cloud gateway 實現簡單路由,實現統一路由轉發 |
主要思路: 首先在認證服務器上面獲取token,在網關上進行驗證,然后通過網關到資源服務器上面獲取資源。因為是自己的項目,所以里面主要是用到密碼模式實現的。
認證服務器
pom.xml
點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zswyhou</artifactId>
<groupId>cn.edu.zswyhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.edu.zswyAuth-8850</groupId>
<artifactId>zswyAuth-8850</artifactId>
<dependencies>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 認證鑒權-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- mysql連接池-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--swagger的依賴-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!--MyBatis-Plus的依賴-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<scope>compile</scope>
</dependency>
<!--hutool工具類-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 加入自己定義的工具模塊-->
<dependency>
<groupId>cn.edu.zswyhou.zswyCommon</groupId>
<artifactId>zswyCommon</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
里面主要是依賴主要是認證鑒權依賴實現認證服務器的,其他依賴是我這個項目所需的依賴
認證服務器主要核心類
點擊查看代碼
package cn.edu.zswyauth.security.config;
import cn.edu.zswyauth.security.exception.WebResponseTranslator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthorizationServerTokenServices tokenService;
@Autowired
@Qualifier("myClientDetailsService")
private ClientDetailsService clientService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置客戶端詳細信息服務
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client-id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder().encode("112233"))
//token的有效期
.accessTokenValiditySeconds(30000)
//刷新令牌的有效期,即refreshToken
.refreshTokenValiditySeconds(40000)
//授權成功后跳轉的地址,授權碼模式用到
.redirectUris("https://www.baidu.com/")
//自動授權配置
.autoApprove(true)
//配置申請的權限范圍
.scopes("all")
//表示授權類型,密碼模式
.authorizedGrantTypes("refresh_token","authorization_code","password");
}
@Bean("myClientDetailsService")
public ClientDetailsService clientDetailsService(DataSource dataSource, PasswordEncoder passwordEncoder) {
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
clientDetailsService.setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
/**
* 令牌訪問端點
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices)
.tokenServices(tokenService)
.allowedTokenEndpointRequestMethods(HttpMethod.POST)
.exceptionTranslator(new WebResponseTranslator());
}
/**
* 令牌訪問端點安全策略
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")//oauth/token_key 公開密鑰
.checkTokenAccess("permitAll()")//oauth/check_token公開
.allowFormAuthenticationForClients();// 允許表單認證
}
}
tokenConfig配置類
點擊查看代碼
package cn.edu.zswyauth.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class TokenConfig {
/**
* 秘鑰串
*/
private static final String SIGNING_KEY = "uaa";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
/**
* 配置令牌管理
*/
@Bean
public AuthorizationServerTokenServices tokenService(ClientDetailsService clientDetailsService, TokenStore tokenStore
, JwtAccessTokenConverter accessTokenConverter) {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);
service.setSupportRefreshToken(true);
service.setTokenStore(tokenStore);
//加入增強器tokenEnhancer
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> list=new ArrayList<TokenEnhancer>();
list.add(myTokenEnhancer());
list.add(accessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(list);
service.setTokenEnhancer(tokenEnhancerChain);
return service;
}
/**
* 授權碼存儲方式,其實這個也可以不寫,因為用的是密碼模式,沒有授權碼
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Bean
public MyTokenEnhancer myTokenEnhancer(){
return new MyTokenEnhancer();
}
}
MytokenEnhancer增強器(增加token里面的信息,信息可以自己定義)
點擊查看代碼
package cn.edu.zswyauth.security.config;
import cn.edu.zswyauth.entity.User;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
/**
*token的增前器,增加payload的字段
*/
public class MyTokenEnhancer implements TokenEnhancer {
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
User user=new User();
Map<String,Object> map=new HashMap<String,Object>();
map.put("user_id",user.getUseId());
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);
return oAuth2AccessToken;
}
}
WebSecurityConfig主要配置類(負責定義攔截哪些路徑或允許哪些路徑通過)
點擊查看代碼
package cn.edu.zswyauth.security.config;
import cn.edu.zswyauth.security.handle.FailureHandler;
import cn.edu.zswyauth.security.handle.LogoutHandler;
import cn.edu.zswyauth.security.handle.SuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SuccessHandler successHandler;
@Autowired
private FailureHandler failureHandler;
@Autowired
private LogoutHandler logoutHandler;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().formLogin()
// .loginProcessingUrl("/login").permitAll()
.successHandler(successHandler).permitAll()
.failureHandler(failureHandler).permitAll().and()
.logout().logoutSuccessHandler(logoutHandler).and()
.authorizeRequests()
.antMatchers("/**").permitAll();
}
}
MyUserDetailService(查詢用戶是否存在)
點擊查看代碼
package cn.edu.zswyauth.security.service;
import cn.edu.zswyauth.entity.User;
import cn.edu.zswyauth.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class MyUserDetailService implements UserDetailsService {
private PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
@Qualifier("MyUserService")
private UserService userService;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> wrapper=new QueryWrapper<User>();
wrapper.eq("user_number",username);
User user = userService.getOne(wrapper);
//判斷用戶是否存在
if (user == null) {
return null;
}
System.out.println(user);
boolean accountNonExpired = user.getUserIsaccountnonexpired() == 1 ? true : false;
boolean accountnonlocked = user.getUserIsaccountnonlocked() == 1 ? true : false;
boolean credentialsnonexpired = user.getUserIscredentialsnonexpired() == 1 ? true : false;
boolean enableds = user.getUserIsenabled() == 1 ? true : false;
//獲取權限
String userRole = user.getUserRole();
System.out.println(userRole);
//返回一個user用戶,注意user是security里的用戶
UserDetails u =new org.springframework.security.core.userdetails.User(user.getUserNumber(), passwordEncoder().encode(user.getUserPassword()), enableds,accountNonExpired,credentialsnonexpired,accountnonlocked, AuthorityUtils.commaSeparatedStringToAuthorityList(userRole));
return u;
}
}
FailureHandler
點擊查看代碼
package cn.edu.zswyauth.security.handle;
import cn.edu.zswyCommon.response.JSONAuthentication;
import cn.edu.zswyCommon.response.Result;
import cn.edu.zswyCommon.response.ResultCode;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("myAuthenticationFailureHandler")
public class FailureHandler extends JSONAuthentication implements AuthenticationFailureHandler {
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Result result = null;
if (e instanceof AccountExpiredException) {
//賬號過期
result = Result.error(ResultCode.USER_ACCOUNT_EXPIRED);
} else if (e instanceof BadCredentialsException) {
//密碼錯誤
result = Result.error(ResultCode.USER_CREDENTIALS_ERROR);
} else if (e instanceof CredentialsExpiredException) {
//密碼過期
result = Result.error(ResultCode.USER_CREDENTIALS_EXPIRED);
} else if (e instanceof DisabledException) {
//賬號不可用
result = Result.error(ResultCode.USER_ACCOUNT_DISABLE);
} else if (e instanceof LockedException) {
//賬號鎖定
result = Result.error(ResultCode.USER_ACCOUNT_LOCKED);
} else if (e instanceof InternalAuthenticationServiceException) {
//用戶不存在
result = Result.error(ResultCode.USER_ACCOUNT_NOT_EXIST);
}else{
//其他錯誤
result = Result.error(ResultCode.COMMON_FAIL);
}
this.WriteJSON(httpServletRequest,httpServletResponse,result);
}
}
LogoutHandler
點擊查看代碼
package cn.edu.zswyauth.security.handle;
import cn.edu.zswyCommon.response.JSONAuthentication;
import cn.edu.zswyCommon.response.Result;
import cn.edu.zswyCommon.response.ResultCode;
import cn.hutool.core.util.StrUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("myLogoutHandler")
public class LogoutHandler extends JSONAuthentication implements LogoutSuccessHandler {
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
Result result = Result.ok(ResultCode.SUCCESS).message("注銷成功");
this.WriteJSON(httpServletRequest,httpServletResponse,result);
}
}
SuccessHandler
點擊查看代碼
@Component("myAuthenticationSuccessHandler")
public class SuccessHandler extends JSONAuthentication implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//只需要以json的格式返回一個提示就行了
//Result類的Json數據
Result result = Result.ok(ResultCode.SUCCESS).message("登錄成功");
this.WriteJSON(httpServletRequest,httpServletResponse,result);
}
}
WebResponseTranslator(獲取token時用戶名錯誤的處理)
點擊查看代碼
public class WebResponseTranslator implements WebResponseExceptionTranslator {
public ResponseEntity translate(Exception e) throws Exception {
if (e instanceof InternalAuthenticationServiceException) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new OAuth2Exception("賬號密碼錯誤"));
}
return ResponseEntity
.status(HttpStatus.OK)
.body(new OAuth2Exception(e.getMessage()));
}
}
zswyAuthMain8850
點擊查看代碼
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("cn/edu/zswyauth/mapper")
public class zswyAuthMain8850 {
public static void main(String[] args) {
SpringApplication.run(zswyAuthMain8850.class,args);
}
}
application.yml
點擊查看代碼
server:
port: 8850
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
application:
name: zswyAuth-8850
cloud:
nacos:
discovery:
server-addr: localhost:8848
#暴露監控
management:
endpoints:
web:
exposure:
include: '*'
網關
pom.xml
點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zswyhou</artifactId>
<groupId>cn.edu.zswyhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.edu.zswyGateway-9527</groupId>
<artifactId>zswyGateway-9527</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--授權-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!-- 網關這個依賴千萬不要加,不然網關的過濾器不起作用-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-oauth2</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<!--hutool工具類-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 加入自己定義的工具模塊-->
<dependency>
<groupId>cn.edu.zswyhou.zswyCommon</groupId>
<artifactId>zswyCommon</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>io.springfox</groupId>
<artifactId>springfox-spring-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
AuthClient(認證客戶端)
點擊查看代碼
@Component
public class AuthClient {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthClient.class);
private RestTemplate restTemplate = new RestTemplate();
private String checkTokenUrl="http://localhost:8850/oauth/check_token";
public HashMap<Object,String> accessable(ServerHttpRequest request, String token) {
//造和編碼URI,默認是utf-8
// 加入請求頭
HttpEntity<?> entity = new HttpEntity<>(request.getHeaders());
ParameterizedTypeReference<String> myBean = new ParameterizedTypeReference<String>() {};
try {
// 調用zswyAuth-8850的check_token,檢查token
ResponseEntity<String> responseEntity = restTemplate.exchange(checkTokenUrl + "?token=" + token, HttpMethod.POST, entity, myBean);
// 日志
LOGGER.info("oauth request: {}, response body: {}, reponse status: {}",
entity, responseEntity.getBody(), responseEntity.getStatusCode());
// 得到返回的數據
String body = responseEntity.getBody();
// 把String轉為HashMap類型
HashMap<Object,String> hashMap = JSON.parseObject(body,HashMap.class);
System.out.println(hashMap);
return hashMap;
} catch (RestClientException e) {
LOGGER.error("oauth failed.", e);
}
return null;
}
}
GatewayCorsConfiguration(網關跨域的配置類)
點擊查看代碼
@Configuration
public class GatewayCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
System.out.println("進入corsWebFilter");
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
MyLogGateWayFilter(全局過濾器)
點擊查看代碼
@Configuration
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
// @Autowired
// @Qualifier("tokenStore")
// private TokenStore tokenStore;
@Autowired
private AuthClient client;
@Autowired
private TokenUntils tokenUntils;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("time:"+new Date()+"\t 執行了自定義的全局過濾器: "+"MyLogGateWayFilter"+"hello");
String requestUrl = exchange.getRequest().getPath().value();
AntPathMatcher pathMatcher = new AntPathMatcher();
//1 auth服務所有放行
if (pathMatcher.match("/oauth/**", requestUrl)) {
return chain.filter(exchange);
}
//2 檢查token是否存在
String token = tokenUntils.getToken(exchange);
if (StringUtils.isBlank(token)) {
return tokenUntils.noTokenMono(exchange);
}else {
HashMap accessable = client.accessable(exchange.getRequest(), token);
//判斷用戶憑證是否存在
if (accessable.get("user_name")==null){
return tokenUntils.invalidTokenMono(exchange);
}
//獲取憑證
Object principal = accessable.get("user_name");
//獲取用戶權限
List<String> authorities = (List<String>) accessable.get("authorities");
JSONObject jsonObject=new JSONObject();
jsonObject.put("principal",principal);
jsonObject.put("authorities",authorities);
//轉為base64編碼
String base64 = EncryptUtil.encodeUTF8StringBase64(jsonObject.toJSONString());
//把base64加入請求頭中
ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header("json-token", base64).build();
ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
return chain.filter(exchange);
}
}
/**
*這個數值越小,越先執行該過濾器
*/
@Override
public int getOrder() {
return 0;
}
}
WebSecurityConfig(Security的配置類)
點擊查看代碼
@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig{
@Bean
public ServerCodecConfigurer serverCodecConfigurer() {
return ServerCodecConfigurer.create();
}
@Bean
SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http){
System.out.println("進入webFluxSecurityFilterChain");
return http.authorizeExchange()
.pathMatchers("/**").permitAll()
.anyExchange().authenticated()
.and().csrf().disable().build();
}
}
TokenUntils(token的工具類)
點擊查看代碼
@Component
public class TokenUntils {
/**
* 獲取token
*/
public String getToken(ServerWebExchange exchange) {
String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(tokenStr)) {
return null;
}
String token = tokenStr.split(" ")[1];
if (StringUtils.isBlank(token)) {
return null;
}
return token;
}
/**
* 無效的token
*/
public Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
JSONObject json = new JSONObject();
json.put("status", HttpStatus.UNAUTHORIZED.value());
json.put("data", "無效的token");
return buildReturnMono(json, exchange);
}
public Mono<Void> noTokenMono(ServerWebExchange exchange) {
JSONObject json = new JSONObject();
json.put("status", HttpStatus.UNAUTHORIZED.value());
json.put("data", "沒有token");
return buildReturnMono(json, exchange);
}
public Mono<Void> buildReturnMono(JSONObject json, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
byte[] bits = json.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定編碼,否則在瀏覽器中會中文亂碼
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
zswyGateway9527(主程序類)
點擊查看代碼
@SpringBootApplication
@EnableDiscoveryClient
public class zswyGateway9527 {
public static void main(String[] args) {
SpringApplication.run(zswyGateway9527.class,args);
}
}
application.yml
點擊查看代碼
server:
port: 9527
spring:
application:
name: zswy-gateway-9527
nacos:
discovery:
server-addr: 8848
cloud:
gateway:
discovery:
locator:
enabled: true #開啟從注冊中心動態創建路由的功能,利用微服務名進行路由
routes:
- id: zswyBlog_routh #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
# uri: http://localhost:8001 #匹配后提供服務的路由地址
uri: lb://zswyBlog-8001 #匹配后提供服務的路由地址,lb為負載均衡的其中一種模式
predicates:
# 斷言,路徑相匹配的進行路由
- Path= /blog/**
- id: zswyAuth-8850_routh #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: lb://zswyAuth-8850 #匹配后提供服務的路由地址
predicates:
# 斷言,路徑相匹配的進行路由
- Path= /oauth/**
# 解決某些錯誤,啟動覆蓋
main:
allow-bean-definition-overriding: true
資源服務器
pom.xml
點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zswyhou</artifactId>
<groupId>cn.edu.zswyhou</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.edu.zswyBlog</groupId>
<artifactId>zswyBlog</artifactId>
<dependencies>
<!-- 加入自己定義的工具模塊-->
<dependency>
<groupId>cn.edu.zswyhou.zswyCommon</groupId>
<artifactId>zswyCommon</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--web-actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- <scope>test</scope>-->
</dependency>
<!-- mysql連接池-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--swagger的依賴-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!--MyBatis-Plus的依賴-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--代碼自動生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<!--添加 模板引擎 依賴,MyBatis-Plus 支持 Velocity(默認)-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<!--hutool工具類-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-core</artifactId>-->
<!--<!– <scope>test</scope>–>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-annotation</artifactId>-->
<!--<!– <scope>compile</scope>–>-->
<!-- </dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 阿里雲儲存OS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<!--授權-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<!--JSONObject-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
</project>
資源服務核心配置類
點擊查看代碼
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
/**
* 資源ID,與認證服務器上的client-id相同
*/
private static final String RESOURCE_ID = "admin";
/**
* 資源配置
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenStore(tokenStore)
.stateless(true)
.accessDeniedHandler(new CustomAccessDeniedHandler());
}
/**
* 請求配置
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").permitAll()
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
TokenConfig
點擊查看代碼
@Configuration
public class TokenConfig {
/**
* 密匙串,一定要與zswyAuth-8850的密匙串一樣,否則校檢不通過
*/
private static final String SIGNING_KEY = "uaa";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
CustomAccessDeniedHandler(沒有權限的操作)
點擊查看代碼
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
JSONObject json=new JSONObject();
json.put("code",403);
json.put("msg","沒有權限");
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.getWriter().write(JSONObject.toJSONString(json));
}
}
AuthenticationFilter(認證過濾器,獲取到傳過來的用戶名和權限)
點擊查看代碼
@Component
public class AuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("進入AuthenticationFilter");
String token = request.getHeader("json-token");
if (StringUtils.isNotBlank(token)){
//base64解碼
String json = EncryptUtil.decodeUTF8StringBase64(token);
//String轉為JSONObject
JSONObject jsonObject = JSON.parseObject(json);
//獲取用戶身份信息
String principal = jsonObject.getString("principal");
//獲取權限信息
JSONArray tempJsonArray = jsonObject.getJSONArray("authorities");
//存入數組中
String[] authorities = tempJsonArray.toArray(new String[0]);
//身份信息、權限信息填充到用戶身份token對象中
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(principal,null,
AuthorityUtils.createAuthorityList(authorities));
//創建details
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//將authenticationToken填充到安全上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request,response);
}
}
zswyBlogMain
點擊查看代碼
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("cn/edu/zswyblog/mapper")
public class zswyBlogMain {
public static void main(String[] args) {
SpringApplication.run(zswyBlogMain.class,args);
}
}
UserController(控制器)
點擊查看代碼
@RestController
@RequestMapping("/blog/use")
public class UserController extends JSONAuthentication {
@Autowired
private BlogMapper blogMapper;
@PostMapping("/use")
//定義權限
@PreAuthorize("hasAnyAuthority('user','ROLE_admin')")
public void get(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
System.out.println("進入get");
Blog blog = blogMapper.selectById(1);
Map<String,Object> map=new HashMap<String, Object>();
map.put("blog",blog);
Result result=Result.ok(ResultCode.SUCCESS).data(map);
this.WriteJSON(request,response,result);
}
}
有nacos的先啟動nacos,沒有的不用啟動,然后依次啟動zswyAuth-8850,zswyBlog,zswyGateway-9527
username和password分別是與認證服務器的client-id和配置client-secret里的值
grant_type 是指定以什么模式進行,password是密碼模式
scope是指定范圍,all為所有范圍
username是用戶名
password是密碼
獲取令牌,此時access_token就是token,你可以用它來訪問一些資源,refresh_token是刷新令牌
然后我們復制這個token到請求頭去請求資源
以下就是資源請求成功后返回的數據
好了,這樣Spring Cloud Gateway 整合 Spring Security Oauth2就好了,如果有錯誤的地方歡迎指正。