Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例
Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验
- 认证授权中心
- 添加依赖
- application.yml配置
- Security配置
- 登录认证配置
- Oauth2参数配置类
- 授权服务配置
- 执行测试
- 增强令牌
- 重构端点
- 重构令牌
- 执行测试
- 资源服务中心
- 认证异常配置
- 创建资源服务
- 提供资源
- 执行测试
- 网关校验
- 添加依赖
- 配置application.yml
- 路径白名单配置类
- 网关过滤器
- 执行测试
)
认证授权中心
添加依赖
<!-- 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.Driverusername: rootpassword: 123456url: jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false# Redisredis:port: 6379host: 127.0.0.1timeout: 3000database: 1password:# Oauth2
client:oauth2:client-id: appId # 客户端标识 IDsecret: 123456 # 客户端安全码# 授权类型grant_types:- password- refresh_token# token 有效时间,单位秒token-validity-time: 2592000refresh-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连接工厂*/@Resourceprivate RedisConnectionFactory redisConnectionFactory;/*** 初始化RedisTokenStore,用于将token存储至Redis** @return*/@Beanpublic RedisTokenStore redisTokenStore() {RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);// 设置key的层级前缀redisTokenStore.setPrefix("TOKEN:");return redisTokenStore;}/*** 初始化密码编码器,指定编码与校验规则,用MD5加密密码** @return*/@Beanpublic PasswordEncoder passwordEncoder() {// Security官方推荐的BCryptPasswordEncoder加密与校验类// 密钥的迭代次数(默认为10)//return new BCryptPasswordEncoder(10);return new PasswordEncoder() {/*** 加密* @param rawPassword 原始密码* @return*/@Overridepublic String encode(CharSequence rawPassword) {return DigestUtil.md5Hex(rawPassword.toString());}/*** 校验密码* @param rawPassword 原始密码* @param encodedPassword 加密密码* @return*/@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword);}};}/*** 初始化认证管理对象** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 放行和认证规则** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {// 禁用csrfhttp.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 {@Resourceprivate UserMapper userMapper;@Overridepublic 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*/@Resourceprivate RedisTokenStore redisTokenStore;/*** 认证管理对象*/@Resourceprivate AuthenticationManager authenticationManager;/*** 密码编码器*/@Resourceprivate PasswordEncoder passwordEncoder;/*** 客户端配置类*/@Resourceprivate ClientOAuth2DataConfiguration clientOAuth2DataConfiguration;/*** 登录校验*/@Resourceprivate UserService userService;/*** 配置令牌端点安全约束** @param security* @throws Exception*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {// 允许访问token的公钥,默认/oauth/token_key是受保护的security.tokenKeyAccess("permitAll()")// 允许检查token的状态,默认/oauth/check_token是受保护的.checkTokenAccess("permitAll()");}/*** 客户端配置 - 授权模型** @param clients* @throws Exception*/@Overridepublic 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*/@Overridepublic 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 {@Resourceprivate TokenEndpoint tokenEndpoint;@Resourceprivate 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*/@Overridepublic 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_USERthis.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");}return this.authorities;}@Overridepublic String getPassword() {return this.password;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return this.isValid != 0;}}
修改登录认证
@Overridepublic 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*/@Overridepublic 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 {@Resourceprivate ObjectMapper objectMapper;/*** 认证失败处理逻辑** @param request* @param response* @param authException* @throws IOException*/@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {// 返回 JSONresponse.setContentType("application/json;charset=utf-8");// 状态码 401response.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 {@Resourceprivate MyAuthenticationEntryPoint authenticationEntryPoint;/*** 配置放行的资源** @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {//所有请求必须认证通过http.authorizeRequests()//其他地址需要认证授权;.anyRequest().authenticated().and()//下边的路径放行.requestMatchers().antMatchers("/user/**");}@Overridepublic 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 {@Resourceprivate 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_tokenif (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: 9999spring:application:name: gateway-servercloud:gateway:discovery:locator:enabled: true # 开启配置注册中心进行路由功能lower-case-service-id: true # 将服务名称转小写routes:- id: oauth2-serveruri: lb://oauth2-serverpredicates:- Path=/auth/**filters:- StripPrefix=1# 自定义参数
secure:ignore:urls: # 配置白名单路径- /actuator/**- /auth/oauth/**- /user/getLoginUser- /user/logout# 配置 Eureka Server 注册中心
eureka:instance:prefer-ip-address: trueinstance-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 {@Resourceprivate IgnoreUrlsConfig ignoreUrlsConfig;@Resourceprivate RestTemplate restTemplate;/*** 身份校验处理** @param exchange* @param chain* @return*/@Overridepublic 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_tokenString 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 {// 发送远程请求,验证 tokenResponseEntity<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*/@Overridepublic int getOrder() {return 0;}@Resourceprivate 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
}
Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例相关推荐
- Spring Security OAuth2.0认证授权知识概括
Spring Security OAuth2.0认证授权知识概括 安全框架基本概念 基于Session的认证方式 Spring Security简介 SpringSecurity详解 分布式系统认证方 ...
- Spring Security OAuth2.0认证授权三:使用JWT令牌
历史文章 [Spring Security OAuth2.0认证授权一:框架搭建和认证测试] [Spring Security OAuth2.0认证授权二:搭建资源服务] 前面两篇文章详细讲解了如何基 ...
- Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
历史文章 [Spring Security OAuth2.0认证授权一:框架搭建和认证测试] [Spring Security OAuth2.0认证授权二:搭建资源服务] [Spring Securi ...
- Spring Security OAuth2.0认证授权
文章目录 1.基本概念 1.1.什么是认证 1.2 什么是会话 1.3什么是授权 1.4授权的数据模型 1.4 RBAC 1.4.1 基于角色的访问控制 2.基于Session的认证方式 3.整合案例 ...
- 【Spring】Spring Security OAuth2 JWT 认证
1.概述 Spring Security OAuth2 JWT 认证服务器配置 Spring Security OAuth2 JWT 资源服务器配置 Spring Security OAuth2 Re ...
- Spring Security OAuth2分布式系统认证解决方案
目录 1 什么是分布式系统 2 分布式认证需求 3 分布式认证方案 3.1 选型分析 3.2 技术方案 4 OAuth2.0 4.1 OAuth2.0介绍 4.2 Spring Cloud Secur ...
- Spring Security+Oauth2四种授权模式
上一篇文章:Spring Security + OAuth2.0项目搭建:https://blog.csdn.net/qq_42402854/article/details/123057625 接着认 ...
- SpringCloud 基于OAth2.0 搭建认证授权中心_02
文章目录 一.数据库部分 1. 创建数据库 2. 初始化数据脚本 二.搭建maven父工程认证授权模块 2.1. 创建一个maven项目 2.2. 引入依赖 三.搭建认证授权模块 3.1. 创建一个子 ...
- Spring Security oAuth2创建认证服务器模块
POM <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http:// ...
- 基于Session的认证方式_实现授权功能_Spring Security OAuth2.0认证授权---springcloud工作笔记118
技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 我们来实现基本的,session的授权功能,很简单实际上就是利用了springmvc的拦截器.不多 ...
最新文章
- 经典的”服务器最多65536个连接”误解
- (转载)文件系统与数据库系统的区别
- Java并发编程——线程池的使用
- 【UOJ34】—多项式乘法(FFT)
- Linux基础书籍推荐
- Tornado web 框架
- 容器rocker_用Rocker制作模板
- lincode 题目记录6
- win7 64位下自行编译OpenCV2.4.10+CUDA toolkit 5.5的整个过程以及需要注意的问题(opencv+cuda并行计算元素的使用)...
- Linux运行shell脚本出现出错,可能是环境编码不一致造成的。
- python取文件后缀
- 【转】脉络清晰的BP神经网络讲解,赞
- java mockserver搭建_使用Moco搭建Mock Server教程
- n维球体,n维椭球体体积公式证明
- Kinect for Windows SDK v2.0 开发笔记 (七)语音识别(上)
- #年轻人找工作应该把钱放第一位吗#
- cocoscreator 使用内置自带的资源和生成单色图片
- Mysql高级命令与概念
- 程序员外包到底怎么了?
- .net对Excel表数据读写操作