認證授權中心
添加依賴
<!-- spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring data redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- spring cloud security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<!-- spring cloud oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<!-- spring cloud 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
複制代碼
application.yml配置
server:
port: 8888 # 端口
spring:
application:
name: oauth2-server # 應用名
# 資料庫
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
# Redis
redis:
port: 6379
host: 127.0.0.1
timeout: 3000
database: 1
password:
# Oauth2
client:
oauth2:
client-id: appId # 用戶端辨別 ID
secret: 123456 # 用戶端安全碼
# 授權類型
grant_types:
- password
- refresh_token
# token 有效時間,機關秒
token-validity-time: 2592000
refresh-token-validity-time: 2592000
# 用戶端通路範圍
scopes:
- api
- all
# Mybatis
mybatis:
configuration:
map-underscore-to-camel-case: true # 開啟駝峰映射
複制代碼
Security配置
配置使用Redis存儲Token資訊
配置密碼的加密、解密、校驗邏輯
初始化認證管理對象
配置請求通路的放行和認證規則
複制代碼
import cn.hutool.crypto.digest.DigestUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.annotation.Resource;
/**
* Security配置
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 注入Redis連接配接工廠
*/
@Resource
private RedisConnectionFactory redisConnectionFactory;
/**
* 初始化RedisTokenStore,用于将token存儲至Redis
*
* @return
*/
@Bean
public RedisTokenStore redisTokenStore() {
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
// 設定key的層級字首
redisTokenStore.setPrefix("TOKEN:");
return redisTokenStore;
}
/**
* 初始化密碼編碼器,指定編碼與校驗規則,用MD5加密密碼
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
// Security官方推薦的BCryptPasswordEncoder加密與校驗類
// 密鑰的疊代次數(預設為10)
//return new BCryptPasswordEncoder(10);
return new PasswordEncoder() {
/**
* 加密
* @param rawPassword 原始密碼
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return DigestUtil.md5Hex(rawPassword.toString());
}
/**
* 校驗密碼
* @param rawPassword 原始密碼
* @param encodedPassword 加密密碼
* @return
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword);
}
};
}
/**
* 初始化認證管理對象
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 放行和認證規則
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用csrf
http.csrf().disable()
.authorizeRequests()
// 放行的請求
.antMatchers("/oauth/**", "/actuator/**").permitAll()
.and()
.authorizeRequests()
// 其他請求必須認證才能通路
.anyRequest().authenticated();
}
}
複制代碼
登入認證配置
建立UserService類實作UserDetailsService類重寫loadUserByUsername方法,該方法主要實作登入、認證校驗邏輯,這裡簡單模拟。
public interface UserMapper {
/**
* 根據使用者名 or 手機号 or 郵箱查詢使用者資訊
* @param account
* @return
*/
@Select("select id, username, phone, email, password, roles from user where " +
"(username = #{account} or phone = #{account} or email = #{account})")
Diners selectByAccountInfo(@Param("account") String account);
}
複制代碼
@Service
public class UserService implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StrUtil.hasBlank(username)) {
throw new RuntimeException("使用者名不可為空");
}
User user= userMapper.selectByAccountInfo(username);
if (user == null) {
throw new UsernameNotFoundException("使用者名或密碼錯誤,請重新輸入");
}
return new User(username, user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
}
}
複制代碼
Oauth2參數配置類
讀取application.yaml檔案中的Oauth2配置資訊,并封裝到ClientOAuth2DataConfiguration類
package com.example.demo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 用戶端配置類
*/
@Component
@ConfigurationProperties(prefix = "client.oauth2")
@Data
public class ClientOAuth2DataConfiguration {
/**
* 用戶端辨別ID
*/
private String clientId;
/**
* 用戶端安全碼
*/
private String secret;
/**
* 授權類型
*/
private String[] grantTypes;
/**
* token有效期
*/
private int tokenValidityTime;
/**
* refresh-token有效期
*/
private int refreshTokenValidityTime;
/**
* 用戶端通路範圍
*/
private String[] scopes;
}
複制代碼
授權服務配置
package com.example.demo.config;
import com.example.demo.service.UserService;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.token.store.redis.RedisTokenStore;
import javax.annotation.Resource;
/**
* 授權服務配置
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
/**
* RedisTokenSore
*/
@Resource
private RedisTokenStore redisTokenStore;
/**
* 認證管理對象
*/
@Resource
private AuthenticationManager authenticationManager;
/**
* 密碼編碼器
*/
@Resource
private PasswordEncoder passwordEncoder;
/**
* 用戶端配置類
*/
@Resource
private ClientOAuth2DataConfiguration clientOAuth2DataConfiguration;
/**
* 登入校驗
*/
@Resource
private UserService userService;
/**
* 配置令牌端點安全限制
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允許通路token的公鑰,預設/oauth/token_key是受保護的
security.tokenKeyAccess("permitAll()")
// 允許檢查token的狀态,預設/oauth/check_token是受保護的
.checkTokenAccess("permitAll()");
}
/**
* 用戶端配置 - 授權模型
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient(clientOAuth2DataConfiguration.getClientId()) // 用戶端辨別 ID
.secret(passwordEncoder.encode(clientOAuth2DataConfiguration.getSecret())) // 用戶端安全碼
.authorizedGrantTypes(clientOAuth2DataConfiguration.getGrantTypes()) // 授權類型
.accessTokenValiditySeconds(clientOAuth2DataConfiguration.getTokenValidityTime()) // token 有效期
.refreshTokenValiditySeconds(clientOAuth2DataConfiguration.getRefreshTokenValidityTime()) // 重新整理 token 的有效期
.scopes(clientOAuth2DataConfiguration.getScopes()); // 用戶端通路範圍
}
/**
* 配置授權以及令牌的通路端點和令牌服務
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 認證器
endpoints.authenticationManager(authenticationManager)
// 具體登入的方法
.userDetailsService(userService)
// token 存儲的方式:Redis
.tokenStore(redisTokenStore);
}
}
複制代碼
執行測試
請求 localhost:8888/oauth/token
參數設定
執行請求
檢視Redis
增強令牌
增強令牌就是豐富、自定義令牌包含的資訊,這部分資訊是用戶端能直接看到的
重構端點
重構/oauth/token端點
/**
* Oauth2控制器
*/
@RestController
@RequestMapping("oauth")
public class OAuthController {
@Resource
private TokenEndpoint tokenEndpoint;
@Resource
private HttpServletRequest request;
/**
* 自定義Token傳回對象
*
* @param principal
* @param parameters
* @return
* @throws HttpRequestMethodNotSupportedException
*/
@PostMapping("token")
public HashMap<String, Object> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken auth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) auth2AccessToken;
Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation());
data.put("accessToken", token.getValue());
data.put("expireIn", token.getExpiresIn());
data.put("scopes", token.getScope());
if (token.getRefreshToken() != null) {
data.put("refreshToken", token.getRefreshToken().getValue());
}
data.put("path", request.getServletPath());
return BaseUtil.back(1, data);
}
}
複制代碼
執行測試
重構令牌
建立SignInIdentity登入認證對象類實作UserDetails
package com.example.demo.model;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 登入認證對象
*/
@Getter
@Setter
public class SignInIdentity implements UserDetails {
/**
* 主鍵
*/
private Integer id;
/**
* 使用者名
*/
private String username;
/**
* 昵稱
*/
private String nickname;
/**
* 密碼
*/
private String password;
/**
* 手機号
*/
private String phone;
/**
* 郵箱
*/
private String email;
/**
* 頭像
*/
private String avatarUrl;
/**
* 角色
*/
private String roles;
/**
* 是否有效 0=無效 1=有效
*/
private int isValid;
/**
* 角色集合, 不能為空
*/
private List<GrantedAuthority> authorities;
/**
* 擷取角色資訊
*
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (StrUtil.isNotBlank(this.roles)) {
String[] strings = this.roles.split(",");
// 擷取資料庫中的角色資訊
this.authorities = Stream.of(strings).map(role -> {
return new SimpleGrantedAuthority(role);
}).collect(Collectors.toList());
} else {
// 如果角色為空則設定為ROLE_USER
this.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
}
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.isValid != 0;
}
}
複制代碼
修改登入認證
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StrUtil.hasBlank(username)) {
throw new RuntimeException("使用者名不可為空");
}
Diners diners = dinersMapper.selectByAccountInfo(username);
if (diners == null) {
throw new UsernameNotFoundException("使用者名或密碼錯誤,請重新輸入");
}
// 初始化登入認證對象
SignInIdentity signInIdentity = new SignInIdentity();
// 拷貝屬性
BeanUtils.copyProperties(diners, signInIdentity);
return signInIdentity;
// return new User(username, diners.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
}
複制代碼
令牌增強
/**
* 配置授權以及令牌的通路端點和令牌服務
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 認證器
endpoints.authenticationManager(authenticationManager)
// 具體登入的方法
.userDetailsService(userService)
// token 存儲的方式:Redis
.tokenStore(redisTokenStore)
// 令牌增強對象,增強傳回的結果
.tokenEnhancer((accessToken, authentication) -> {
// 擷取登入使用者的資訊,然後設定
SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("nickname", signInIdentity.getNickname());
map.put("avatarUrl", signInIdentity.getAvatarUrl());
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
token.setAdditionalInformation(map);
return token;
});
}
複制代碼
執行測試
請求 localhost:8888/oauth/token
資源服務中心
登入成功,得到token,通過token擷取資源
認證異常配置
建立MyAuthenticationEntryPoint類,處理認證失敗出現異常時的處理邏輯。
package com.example.demo.config;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.utils.BaseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
/**
* 認證失敗處理
*/
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Resource
private ObjectMapper objectMapper;
/**
* 認證失敗處理邏輯
*
* @param request
* @param response
* @param authException
* @throws IOException
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
// 傳回 JSON
response.setContentType("application/json;charset=utf-8");
// 狀态碼 401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 寫出
PrintWriter out = response.getWriter();
String errorMessage = authException.getMessage();
if (StrUtil.isBlank(errorMessage)) {
errorMessage = "登入失效!";
}
HashMap<String, Object> result = BaseUtil.back(0, errorMessage, errorMessage);
// ResultInfo result = ResultInfoUtil.buildError(ApiConstant.ERROR_CODE, errorMessage, request.getRequestURI());
out.write(objectMapper.writeValueAsString(result));
out.flush();
out.close();
}
}
複制代碼
建立資源服務
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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 javax.annotation.Resource;
/**
* 資源服務
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Resource
private MyAuthenticationEntryPoint authenticationEntryPoint;
/**
* 配置放行的資源
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//所有請求必須認證通過
http.authorizeRequests()
//其他位址需要認證授權;
.anyRequest()
.authenticated()
.and()
//下邊的路徑放行
.requestMatchers()
.antMatchers("/user/**");
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.authenticationEntryPoint(authenticationEntryPoint);
}
}
複制代碼
提供資源
package com.example.demo.controller;
import com.example.demo.model.SignInIdentity;
import com.example.demo.utils.BaseUtil;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
/**
* 使用者中心
*/
@RestController
public class UserController {
@Resource
private RedisTokenStore redisTokenStore;
/**
* 擷取登入使用者的資訊
*
* @param authentication
* @return
*/
@GetMapping("user/getLoginUser")
public HashMap<String, Object> getCurrentUser(Authentication authentication) {
SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
HashMap<String, Object> map = new HashMap<>();
map.put("username", signInIdentity.getUsername());
map.put("phone", signInIdentity.getPhone());
map.put("email", signInIdentity.getEmail());
return BaseUtil.back(1, "擷取資源成功", map);
}
/**
* 安全退出
*
* @param access_token
* @param authorization
* @return
*/
@GetMapping("user/logout")
public HashMap<String, Object> logout(String access_token, String authorization) {
// 判斷access_token是否為空,為空将authorization指派給access_token
if (StringUtils.isBlank(access_token)) {
access_token = authorization;
}
// 判斷authorization是否為空
if (StringUtils.isBlank(access_token)) {
return BaseUtil.back(1, "退出成功");
}
// 判斷bearer token是否為空
if (access_token.toLowerCase().contains("bearer ".toLowerCase())) {
access_token = access_token.toLowerCase().replace("bearer ", "");
}
// 清除redis token資訊
OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(access_token);
if (oAuth2AccessToken != null) {
redisTokenStore.removeAccessToken(oAuth2AccessToken);
OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
redisTokenStore.removeRefreshToken(refreshToken);
}
return BaseUtil.back(1, "退出成功");
}
}
複制代碼
執行測試
請求localhost:8888/oauth/token擷取token
{
"code": 1,
"message": "Successful.",
"data": {
"nickname": "test",
"avatarUrl": "/test",
"accessToken": "2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
"expireIn": 2588653,
"scopes": [
"api"
],
"refreshToken": "154aefe0-a0fa-43d4-91fb-c70b1e2998e4",
"path": "/oauth/token"
}
}
複制代碼
使用token擷取服務資源,有兩種方式:
方式一:
請求localhost:8888/user/getLoginUser?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4擷取資源
{
"msg": "擷取資源成功",
"code": 1,
"data": {
"phone": "13666666666",
"email": null,
"username": "test"
}
}
複制代碼
方式二:
請求localhost:8888/user/getLoginUser,使用Bearer auth認證
擷取資源
{
"msg": "擷取資源成功",
"code": 1,
"data": {
"phone": "13666666666",
"email": null,
"username": "test"
}
}
複制代碼
校驗token
請求 localhost:8888/oauth/check_token?token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4
校驗token成功時:
{
"avatarUrl": "/test",
"user_name": "test",
"scope": [
"api"
],
"nickname": "test",
"active": true,
"exp": 1629734432,
"authorities": [
"ROLE_USER"
],
"client_id": "appId"
}
複制代碼
校驗token失敗時:
{
"error": "invalid_token",
"error_description": "Token was not recognised"
}
複制代碼
安全退出
1.請求localhost:8888/user/logout?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4
2.請求localhost:8888/user/logout,使用Bearer auth認證
{
"msg": "退出成功",
"code": 1,
"data": null
}
複制代碼
退出後再次請求資源
{
"msg": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
"code": 0,
"data": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4"
}
複制代碼
網關校驗
添加依賴
<!-- spring cloud gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
複制代碼
配置application.yml
server:
port: 9999
spring:
application:
name: gateway-server
cloud:
gateway:
discovery:
locator:
enabled: true # 開啟配置注冊中心進行路由功能
lower-case-service-id: true # 将服務名稱轉小寫
routes:
- id: oauth2-server
uri: lb://oauth2-server
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
# 自定義參數
secure:
ignore:
urls: # 配置白名單路徑
- /actuator/**
- /auth/oauth/**
- /user/getLoginUser
- /user/logout
# 配置 Eureka Server 注冊中心
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:8080/eureka/
複制代碼
路徑白名單配置類
/**
* 網關白名單配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "secure.ignore")
public class IgnoreUrlsConfig {
private List<String> urls;
}
複制代碼
網關過濾器
package com.example.demo.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.utils.BaseUtil;
import com.example.demo.config.IgnoreUrlsConfig;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* 網關全局過濾器
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Resource
private IgnoreUrlsConfig ignoreUrlsConfig;
@Resource
private RestTemplate restTemplate;
/**
* 身份校驗處理
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 判斷目前的請求是否在白名單中
AntPathMatcher pathMatcher = new AntPathMatcher();
boolean flag = false;
String path = exchange.getRequest().getURI().getPath();
for (String url : ignoreUrlsConfig.getUrls()) {
if (pathMatcher.match(url, path)) {
flag = true;
break;
}
}
// 白名單放行
if (flag) {
return chain.filter(exchange);
}
// 擷取 access_token
String access_token = exchange.getRequest().getQueryParams().getFirst("access_token");
// 判斷access_token是否為空
if (StringUtils.isBlank(access_token)) {
return this.writeError(exchange, "請登入");
}
// 校驗token是否有效
String checkTokenUrl = "http://oauth2-server/oauth/check_token?token=".concat(access_token);
try {
// 發送遠端請求,驗證 token
ResponseEntity<String> entity = restTemplate.getForEntity(checkTokenUrl, String.class);
// token無效業務邏輯處理
if (entity.getStatusCode() != HttpStatus.OK) {
return this.writeError(exchange, "請求失敗");
}
if (StringUtils.isBlank(entity.getBody())) {
return this.writeError(exchange, "擷取token失敗");
}
} catch (Exception e) {
return this.writeError(exchange, "token校驗失敗");
}
// 放行
return chain.filter(exchange);
}
/**
* 網關過濾器的排序,數字越小優先級越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
@Resource
private ObjectMapper objectMapper;
public Mono<Void> writeError(ServerWebExchange exchange, String error) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
HashMap<String, Object> back = BaseUtil.back(0, error);
String resultInfoJson;
DataBuffer buffer = null;
try {
resultInfoJson = objectMapper.writeValueAsString(back);
buffer = response.bufferFactory().wrap(resultInfoJson.getBytes(StandardCharsets.UTF_8));
} catch (JsonProcessingException ex) {
ex.printStackTrace();
}
return response.writeWith(Mono.just(buffer));
}
}
複制代碼
執行測試
請求localhost:9999/auth/oauth/token擷取token
{
"code": 1,
"message": "Successful.",
"data": {
"nickname": "test",
"avatarUrl": "/test",
"accessToken": "7a3d7102-39eb-4d02-be4c-9a705c9db616",
"expireIn": 2591999,
"scopes": [
"api"
],
"refreshToken": "6c974314-a13c-473a-8cdc-fb8373b3cce5",
"path": "/oauth/token"
}
}
複制代碼
請求localhost:9999/auth/user/getLoginUser?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616擷取服務資源
{
"msg": "擷取資源成功",
"code": 1,
"data": {
"phone": "13666666666",
"email": null,
"username": "test"
}
}
複制代碼
請求localhost:9999/auth/user/logout?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616安全退出
{
"msg": "退出成功",
"code": 1,
"data": null
}