目录
一、黑马程序员Java进阶教程快速入门Spring Security OAuth2.0认证授权详解
1、oauth服务
WebSecurityConfig
TokenConfig
AuthorizationServer
改写密码校验逻辑实现类
2、oauth2支持的四种方式:
3、oauth2授权
ResouceServerConfig
TokenConfig
4、gateway
SecurityWebFilterChain 放行 后面的授权配置会校验(授权配置也有访问控制)
TokenConfig
WebSecurityConfig
设置上下文
二、学成在线
1、GatewayAuthFilter
一、黑马程序员Java进阶教程快速入门Spring Security OAuth2.0认证授权详解
1、oauth服务
WebSecurityConfig
fuction:管理访问控制及哪些请求需要认证,以及需要哪些权限
package com.example.config;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// @Bean
// public UserDetailsService userDetailsService() {
// //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
// manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
// return manager;
// }
@Bean
public PasswordEncoder passwordEncoder() {
// //密码为明文方式
// return NoOpPasswordEncoder.getInstance();
//spring用于加密的一个算法
//授权码模式必须是加密形式
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/r1").hasAnyAuthority("p1")
.antMatchers().authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
;
}
}
TokenConfig
jwt的相关配置就是解析生成jwt
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* @author Mr.Lan
* @version 1.0
* @ClassName TokenConfig$
* @description TODO
* @date 2024/5/21 16:59
**/
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "mq123";
// @Bean
// public TokenStore tokenStore() {
// return new InMemoryTokenStore();
// }
//定义token存储方式
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
//定义jwt校验
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
AuthorizationServer
授权管理:主要是oauth2的配置
如:
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints .authenticationManager(authenticationManager) .authorizationCodeServices(authorizationCodeServices) .tokenServices(tokenService()) .allowedTokenEndpointRequestMethods(HttpMethod.POST); }
.authenticationManager(authenticationManager)在security中引入 和security联系起来(密码认证)
.authorizationCodeServices(authorizationCodeServices)支持授权码认证
.tokenServices(tokenService())令牌配置
@Override public void configure(AuthorizationServerSecurityConfigurer security) { security .tokenKeyAccess("permitAll()") .checkTokenAccess("permitAll()") .allowFormAuthenticationForClients() ; }
这个就是oauth2支持的接口以及允许哪些请求
生成token、校验token
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // // clients.withClientDetails(clientDetailsService); //后面实现clientDetailsService 注入bean后取用 clients.withClientDetails(clientDetailsService); //客户端信息用内存方式 // clients.inMemory()// 使用in‐memory存储 // .withClient("c1")// client_id // .secret(new BCryptPasswordEncoder().encode("secret")) // .resourceIds("res1")//资源id // .authorizedGrantTypes("authorization_code", // "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials // .scopes("all")// 允许的授权范围与服务端匹配 // .autoApprove(false) // //加上验证回调地址 // .redirectUris("http://www.baidu.com"); //注释掉内存客户端自己配置客户端 }
这里的配置信息用数据库存 也可以直接配置 如果客户端一个
@Bean public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) { //采用jdbc模式 自动存放在oauth_code表中 封装类实现好的 return new JdbcAuthorizationCodeServices(dataSource);
授权码用数据库存
配置完后就可以生成token以及校验token
改写密码校验逻辑实现类
package com.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.entity.LoginUser;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.sql.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
/**
* @author Mr.Lan
* @version 1.0
* @ClassName UserDetailsServiceImpl$
* @description TODO
* @date 2024/5/17 15:52
**/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//传参是用户姓名
//数据库查询用户信息以及权限信息
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>();
userLambdaQueryWrapper.eq(User::getUsername,s);
User user = userMapper.selectOne(userLambdaQueryWrapper);
if( ObjectUtils.isEmpty(user)){
throw new RuntimeException("用户不存在");
}
//查询权限信息
ArrayList<String> permissions = new ArrayList<>(Arrays.asList("getUser", "getUser1"));
LoginUser loginUser = new LoginUser(user,permissions);
//返回UserDeatil对象
//返回接口的实现类相当于返回了接口
return loginUser;//返回后后面会校验密码
}
}
package com.example.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.MapUtils;
import com.alibaba.spring.util.ObjectUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.LoginUser;
import com.example.entity.Result;
import com.example.entity.User;
import com.example.service.UserService;
import com.example.mapper.UserMapper;
import com.example.utils.RedisTemplateUtils;
import io.jsonwebtoken.Claims;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapProperties;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import javax.xml.crypto.dsig.keyinfo.RetrievalMethod;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static com.example.utils.JwtUtils.generateJwt;
/**
* @author Admin
* @description 针对表【user】的数据库操作Service实现
* @createDate 2024-05-16 21:03:01
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService{
@Autowired
AuthenticationManager authenticationManager;
@Autowired
UserMapper userMapper;
@Autowired
RedisTemplateUtils redisTemplateUtils;
//authenticationManager在login的方法(这里是实现类)调用,就继续传递
@Override
public Result login(User user) {
//当参数是接口时可以传接口的实现类 创建实现类封装传递 Authentication
//new UsernamePasswordAuthenticationToken()的两个参数 Object 后面要用
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
//这里将返回的时认证后的结果
Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if(Objects.isNull(authenticate)){
throw new RuntimeException("认证失败");
}
BeanUtils.copyProperties(authenticate.getPrincipal(),user);
//获取返回中的信息 加密返回token
HashMap<String, Object> claims = new HashMap<>();
LoginUser principal = (LoginUser)authenticate.getPrincipal();
claims.put("userId",principal.getUser().getId());
String token = generateJwt(claims);
//将token存入redis 并以userId为key
redisTemplateUtils.set("login:"+principal.getUser().getId(),principal,432000L);
HashMap<String, String> map = new HashMap<>();
map.put("token",token);
// String jsonString = JSON.toJSONString(map);
return Result.success(400,"登陆成功",map);
}
@Override
public Result loginOut() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser principal = (LoginUser)authentication.getPrincipal();
String userId = principal.getUser().getId();
//删除redis中的token
try {
redisTemplateUtils.del("login:"+userId);
} catch (Exception e) {
e.printStackTrace();
return Result.error("退出登录失败");
}
return Result.success("成功退出登录");
}
}
2、oauth2支持的四种方式:
###授权码模式
###申请授权码
GET {{auth1}}/auth/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
###申请令牌
POST {{auth1}}/auth/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=yHKHC1&redirect_uri=http://www.baidu.com
Content-Type: application/json
{}
###简化模式
GET http://localhost:8041/auth/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com
Accept: application/json
###密码模式
POST {{auth1}}/auth1/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=lanjie&password=lanjie
Accept: application/json
###客户端模式
POST {{auth1}}/auth/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials
Accept: application/json
授权码、密码、简化、客户端
3、oauth2授权
ResouceServerConfig
资源服务授权(先校验token后授权) 主要是授权
package cn.itcast.order.config;
import cn.itcast.order.filter.TokenAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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 org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author Mr.Lan
* @version 1.0
* @ClassName ResouceServerConfig$
* @description TODO
* @date 2024/5/22 13:28
**/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResouceServerConfigJwt extends ResourceServerConfigurerAdapter {
//资源服务标识
public static final String RESOURCE_ID = "res1";
@Autowired
TokenStore tokenStore;
@Autowired
TokenAuthenticationFilter tokenAuthenticationFilter;
//服务资源配置
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
// .tokenServices(tokenService())
.tokenStore(tokenStore)
.stateless(true);
}
//服务端访问控制
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/*").access("#oauth2.hasAnyScope('all')")
.antMatchers("/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll();
// http.addFilterBefore(tokenAuthenticationFilter, OAuth2AuthenticationProcessingFilter.class);
}
//服务端解析令牌通过远程调用
// @Bean
// public ResourceServerTokenServices tokenService() {
使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
// RemoteTokenServices service=new RemoteTokenServices();
// service.setCheckTokenEndpointUrl("http://localhost:8041/auth/oauth/check_token");
// service.setClientId("c1");
// service.setClientSecret("secret");
// return service;
// }
}
TokenConfig
package cn.itcast.order.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* @author Administrator
* @version 1.0
**/
@Configuration
public class TokenConfigJwt {
String SIGNING_KEY = "mq123";
// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
4、gateway
SecurityWebFilterChain 放行 后面的授权配置会校验(授权配置也有访问控制)
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @author Mr.Lan
* @version 1.0
* @ClassName ResouceServerConfig$
* @description TODO
* @date 2024/5/22 16:32
**/
@Configuration
public class ResouceServerConfig {
public static final String RESOURCE_ID = "res1";
/**
* 统一认证服务(UAA) 资源拦截
*/
@Configuration
@EnableResourceServer
public class UAAServerConfig extends
ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources){
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/uaa/**").permitAll();
}
}
/**
* 订单服务
*/
@Configuration
@EnableResourceServer
public class OrderServerConfig extends
ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/order/**").access("#oauth2.hasScope('ROLE_API')");
}
}
}
TokenConfig
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* @author Mr.Lan
* @version 1.0
* @ClassName TokenConfig$
* @description TODO
* @date 2024/5/21 16:59
**/
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "mq123";
// @Bean
// public TokenStore tokenStore() {
// return new InMemoryTokenStore();
// }
//定义token存储方式
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
//定义jwt校验
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
WebSecurityConfig
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@EnableWebFluxSecurity
@Configuration
public class WebSecurityConfig {
//安全拦截配置
@Bean
public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/**").permitAll()
.anyExchange().authenticated()
.and().csrf().disable().build();
}
}
ps:改视频使用的是网关与服务之间进行明文token(记得设置资源的访问控制为都放行,网关以及认证授权了)
设置上下文
SecurityContextHolder.getContext()
package cn.itcast.order.filter;
import cn.itcast.order.pojo.UserDTO;
import cn.itcast.order.utils.EncryptUtil;
import cn.itcast.order.utils.HeaderMapRequestWrapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* @author Mr.Lan
* @version 1.0
* @ClassName TokenAuthenticationFilter$
* @description TODO
* @date 2024/5/23 11:47
**/
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TokenAuthenticationFilter extends OncePerRequestFilter{
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse
httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
String token=null;
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = httpServletRequest.getHeader(headerName);
if(headerName.equals("json-token")){
token=headerValue;
}
// 处理请求头信息
log.info("{}:{}",headerName,headerValue);
}
String jwt=httpServletRequest.getHeader("jwt");
//原有的请求头依然存在
HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(httpServletRequest);
requestWrapper.addHeader("Authorization",jwt);
// String token = httpServletRequest.getHeader("json‐token");
if (token != null){
//1.解析token
String json = EncryptUtil.decodeUTF8StringBase64(token);
JSONObject userJson = JSON.parseObject(json);
UserDTO user = new UserDTO();
user.setUsername(userJson.getString("principal"));
JSONArray authoritiesArray = userJson.getJSONArray("authorities");
String [] authorities = authoritiesArray.toArray( new
String[authoritiesArray.size()]);
// 2.新建并填充authentication
UsernamePasswordAuthenticationToken authentication = new
UsernamePasswordAuthenticationToken(
user, null, AuthorityUtils.createAuthorityList(authorities));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
httpServletRequest));
//3.将authentication保存进安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(requestWrapper, httpServletResponse);
}
}
上下文如果授权配置ResouceServerConfig生效 会自动生成上下文
而资源服务已经关闭了授权配置 因此上下文需要明文token传递 然后解析
目的是 资源服务可以借此获取用户信息
完结!!!!
网关的token是否会转发
二、学成在线
区别: 在网关设置了全局过滤器实现白名单以及校验 没有使用资源配置校验
1、GatewayAuthFilter
package com.xuecheng.gateway.config;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* @author Mr.M
* @version 1.0
* @description 网关认证过虑器
* @date 2022/9/27 12:10
*/
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {
//白名单
private static List<String> whitelist = null;
static {
//加载白名单
try (
InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
) {
Properties properties = new Properties();
properties.load(resourceAsStream);
Set<String> strings = properties.stringPropertyNames();
whitelist= new ArrayList<>(strings);
} catch (Exception e) {
log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
e.printStackTrace();
}
}
@Autowired
private TokenStore tokenStore;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestUrl = exchange.getRequest().getPath().value();
AntPathMatcher pathMatcher = new AntPathMatcher();
//白名单放行
for (String url : whitelist) {
if (pathMatcher.match(url, requestUrl)) {
return chain.filter(exchange);
}
}
//检查token是否存在
String token = getToken(exchange);
if (StringUtils.isBlank(token)) {
return buildReturnMono("没有认证",exchange);
}
//判断是否是有效的token
OAuth2AccessToken oAuth2AccessToken;
try {
oAuth2AccessToken = tokenStore.readAccessToken(token);
boolean expired = oAuth2AccessToken.isExpired();
if (expired) {
return buildReturnMono("认证令牌已过期",exchange);
}
return chain.filter(exchange);
} catch (InvalidTokenException e) {
log.info("认证令牌无效: {}", token);
return buildReturnMono("认证令牌无效",exchange);
}
}
/**
* 获取token
*/
private 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;
}
private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(new RestErrorResponse(error));
byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return 0;
}
}
未完!
有关springsecurity单独实现认证授权