1. 题记
考虑使用gateway项目结合security的方式去做个网关拦截服务,某度一搜一片,基本都是不可以用的,而且问题不少,为此我自己也是东拼西凑,目前勉强搞了一套可以临时用的,先记录一下,后续优化会持续更新。
2. 看此文章前需要具备的条件
- 了解security基本使用方式(不了解可以先去看# B站三更草堂,2小时就可以入门)
- 了解响应式/反应式编程,与常规HTTP编程的区别(B站找尚硅谷或者黑马都可以,2小时看完入门)
- 了解spring boot/cloud/gateway等组件基本使用(这个自己百度看看帖子即可)
- 所以此篇文章跨度会比较大
3. 版本参考
<properties>
<java.version>11</java.version>
<revision>1.0.0-SNAPSHOT</revision>
<maven.compile.source>11</maven.compile.source>
<maven.compile.target>11</maven.compile.target>
<spring-cloud-version>2020.0.1</spring-cloud-version>
<spring-alibaba-cloud-version>2021.1</spring-alibaba-cloud-version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
</parent>
4. 代码讲解
4.1 引入依赖
<!-- 引入gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 鉴权security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
4.2 引入基于webflux版本的security的配置文件
package cn.com.dzod.pm.gateway.security.conf;
import cn.com.dzod.pm.auth.interfaces.auth.dto.SysUserDto;
import cn.com.dzod.pm.gateway.security.check.AuthenticationConverter;
import cn.com.dzod.pm.gateway.security.check.AuthenticationManager;
import cn.com.dzod.pm.gateway.security.check.AuthorizeConfigManager;
import cn.com.dzod.pm.gateway.security.check.MySqlReactiveUserDetailsServiceImpl;
import cn.com.dzod.pm.gateway.security.exception.AuthEntryPointException;
import cn.com.dzod.pm.gateway.security.handle.JsonServerAuthenticationFailureHandler;
import cn.com.dzod.pm.gateway.security.handle.JsonServerAuthenticationSuccessHandler;
import cn.com.dzod.pm.gateway.security.handle.JsonServerLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import java.util.LinkedList;
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Autowired
private AuthenticationConverter authenticationConverter;
@Autowired
private AuthorizeConfigManager authorizeConfigManager;
@Autowired
private AuthEntryPointException serverAuthenticationEntryPoint;
@Autowired
private JsonServerAuthenticationSuccessHandler jsonServerAuthenticationSuccessHandler;
@Autowired
private JsonServerAuthenticationFailureHandler jsonServerAuthenticationFailureHandler;
@Autowired
private JsonServerLogoutSuccessHandler jsonServerLogoutSuccessHandler;
@Autowired
private AuthenticationManager authenticationManager;
private static final String[] AUTH_WHITELIST = new String[]{"/login", "/logout"};
// @Autowired
// private MySqlReactiveUserDetailsServiceImpl mySqlReactiveUserDetailsService;
// @Autowired
// private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
SecurityWebFilterChain chain = http
.formLogin()
.loginPage("/login")
// .securityContextRepository(new JwtSecurityContextRepository(mySqlReactiveUserDetailsService))
// 登录成功handler
.authenticationSuccessHandler(jsonServerAuthenticationSuccessHandler)
// 登陆失败handler
.authenticationFailureHandler(jsonServerAuthenticationFailureHandler)
// 无访问权限handler
.authenticationEntryPoint(serverAuthenticationEntryPoint)
.and()
.logout()
// 登出成功handler
.logoutSuccessHandler(jsonServerLogoutSuccessHandler)
.and()
.csrf().disable()
.cors().disable()
.httpBasic().disable()
.authorizeExchange()
// 白名单放行
.pathMatchers(AUTH_WHITELIST).permitAll()
// 访问权限控制
.anyExchange().access(authorizeConfigManager)
.and().build();
// 设置自定义登录参数转换器
chain.getWebFilters()
.filter(webFilter -> webFilter instanceof AuthenticationWebFilter)
.subscribe(webFilter -> {
AuthenticationWebFilter filter = (AuthenticationWebFilter) webFilter;
filter.setServerAuthenticationConverter(authenticationConverter);
});
return chain;
}
/**
* 注册用户信息验证管理器,可按需求添加多个按顺序执行
* @return
*/
@Bean
ReactiveAuthenticationManager reactiveAuthenticationManager() {
LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
managers.add(authenticationManager);
return new DelegatingReactiveAuthenticationManager(managers);
}
/**
* BCrypt密码编码
* @return
*/
@Bean
public BCryptPasswordEncoder bcryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SysUserDto sysUserEntity(){
return new SysUserDto();
}
@Bean
MySqlReactiveUserDetailsServiceImpl reactiveUserDetailsService(){ //6
return new MySqlReactiveUserDetailsServiceImpl();
}
}
4.3 引入封装security用户的类
package cn.com.dzod.pm.gateway.security.check;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
@Component("authenticationConverter")
public class AuthenticationConverter extends ServerFormLoginAuthenticationConverter {
private String usernameParameter = "username";
private String passwordParameter = "password";
// private MySqlReactiveUserDetailsServiceImpl userDetailsService;
@Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
// HttpHeaders headers = exchange.getRequest().getHeaders();
return exchange.getFormData()
.map(data -> {
// String username = data.getFirst(this.usernameParameter);
// String password = data.getFirst(this.passwordParameter);
// UserDetails userDetails = userDetailsService.findByUsername(data.getFirst(this.usernameParameter));
return new UsernamePasswordAuthenticationToken(data.getFirst(this.usernameParameter),data.getFirst(this.passwordParameter), Collections.EMPTY_LIST);
// return null;
});
}
}
4.4 紧接着引入验证用户的类
package cn.com.dzod.pm.gateway.security.check;
import cn.com.dzod.pm.gateway.security.dto.AuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
@Component
public class AuthenticationManager extends AbstractUserDetailsReactiveAuthenticationManager {
private Scheduler scheduler = Schedulers.boundedElastic();
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Autowired
private MySqlReactiveUserDetailsServiceImpl mySqlReactiveUserDetailsService;
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
// UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
final String username = authentication.getName();
final String presentedPassword = (String) authentication.getCredentials();
return retrieveUser(username)
.publishOn(scheduler)
.filter(u -> passwordEncoder.matches(presentedPassword, u.getPassword()))
.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
.flatMap(u -> {
boolean upgradeEncoding = mySqlReactiveUserDetailsService != null
&& passwordEncoder.upgradeEncoding(u.getPassword());
if (upgradeEncoding) {
String newPassword = passwordEncoder.encode(presentedPassword);
return mySqlReactiveUserDetailsService.updatePassword(u, newPassword);
}
return Mono.just(u);
})
.flatMap(userDetails -> {
// 省略业务代码
return Mono.just(userDetails);
})
.map(u -> new AuthenticationToken(u, u.getPassword(), u.getAuthorities()));
}
@Override
protected Mono<UserDetails> retrieveUser(String username) {
return mySqlReactiveUserDetailsService.findByUsername(username);
}
}
4.5 用户验证过了以后就需要鉴权
package cn.com.dzod.pm.gateway.security.check;
import cn.com.dzod.pm.gateway.security.dto.MyUserDetailsImpl;
import cn.com.dzod.pm.gateway.util.JwtUtil;
import cn.com.dzod.pm.gateway.util.RedisCache;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class AuthorizeConfigManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
private RedisCache redisCache;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication,
AuthorizationContext authorizationContext) {
return authentication.map(auth -> {
ServerWebExchange exchange = authorizationContext.getExchange();
ServerHttpRequest request = exchange.getRequest();
// todo 增加一份token认证的拦截
String token = request.getHeaders().getFirst("token");
if (token == null){
return new AuthorizationDecision(false);
}
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
}catch (Exception e){
log.error("token 非法");
return new AuthorizationDecision(false);
}
String redisKey = "login:" + userid;
MyUserDetailsImpl loginUser = redisCache.getCacheObject(redisKey);
// Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
for (String url : loginUser.getSysUserDto().getUrls()) {
String path = request.getURI().getPath();
if (antPathMatcher.match(url, path)) {
log.info(String.format("用户请求API校验通过,GrantedAuthority:{%s} Path:{%s} ", url, path));
return new AuthorizationDecision(true);
}
}
return new AuthorizationDecision(false);
}).defaultIfEmpty(new AuthorizationDecision(false));
}
@Override
public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) {
return check(authentication, object)
.filter(d -> d.isGranted())
.switchIfEmpty(Mono.defer(() -> {
// AjaxResult<String> ajaxResult = AjaxResult.restResult("当前用户没有访问权限! ", ApiErrorCode.FAILED);
String body = JSONObject.toJSONString("当前用户没有访问权限");
return Mono.error(new AccessDeniedException(body));
}))
.flatMap(d -> Mono.empty());
}
}
到此为止一套最简单的鉴权流程就已经走完了,这篇文章更多是为了开箱即用,后续就更多是对代码的复制粘贴了
4.6 补全所有pom
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- nacos服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.15</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.15</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.11</version>
</dependency>
<dependency>
<groupId>cn.com.dzod.pm.api</groupId>
<artifactId>pm-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
4.7 准备工具类
4.7.1 JWT
package cn.com.dzod.pm.gateway.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "sangeng";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
4.7.2 Redis工具类
package cn.com.dzod.pm.gateway.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
4.7.3 redis 配置文件
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
4.7.4 主键生成器
import com.github.wujun234.uid.impl.CachedUidGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class IdGenerator {
@Autowired
private CachedUidGenerator cachedUidGenerator;
/**
* 获取uid
*
* @return
*/
public long nextId() {
return cachedUidGenerator.getUID();
}
/**
* 格式化传入的uid,方便查看其实际含义
*
* @param uid
* @return
*/
public String parse(long uid) {
return cachedUidGenerator.parseUID(uid);
}
}
4.8 用户信息封装
package cn.com.dzod.pm.gateway.security.dto;
import cn.com.dzod.pm.auth.interfaces.auth.dto.SysUserDto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
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;
/**
* @Author 许权
* @Date 2021/12/6
*/
@NoArgsConstructor
@Data
//@AllArgsConstructor
public class MyUserDetailsImpl implements UserDetails {
private SysUserDto sysUserDto;
private List<String> permissions;
public MyUserDetailsImpl(SysUserDto sysUserDto, List<String> permissions){
this.sysUserDto = sysUserDto;
this.permissions = permissions;
}
// private boolean enabled;
//存储SpringSecurity所需要的权限信息的集合
@JSONField(serialize = false)
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities!=null){
return authorities;
}
//把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
authorities = permissions.stream().
map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;
}
@Override
public String getPassword() {
return sysUserDto.getPassword();
}
@Override
public String getUsername() {
return sysUserDto.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// @Override
// public boolean isEnabled() {
// return enabled;
// }
}
package cn.com.dzod.pm.gateway.security.check;
import cn.com.dzod.pm.auth.interfaces.auth.dto.SysUserDto;
import cn.com.dzod.pm.auth.interfaces.auth.service.LoginService;
import cn.com.dzod.pm.gateway.security.dto.MyUserDetailsImpl;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import reactor.core.publisher.Mono;
import java.util.List;
@Slf4j
//@Component("mySqlReactiveUserDetailsService")
@Data
public class MySqlReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService, ReactiveUserDetailsPasswordService {
// @Resource
// private SysUserMapper sysUserMapper;
@DubboReference(check = false)
private LoginService loginService;
/**
* 获取指定用户信息与登录信息进行验证
* @param username
* @return
*/
@Override
public Mono<UserDetails> findByUsername(String username) {
SysUserDto sysUserDto = loginService.getUserDto(username);
if (null == sysUserDto) {
throw new UsernameNotFoundException("用户不存在");
}
List<String> permissionList = loginService.getPermissionList(username);
log.info("登录用户权限列表:{}", JSON.toJSONString(permissionList));
return Mono.just(new MyUserDetailsImpl(sysUserDto,permissionList));
}
@Override
public Mono<UserDetails> updatePassword(UserDetails userDetails, String s) {
System.out.println("进入修改密码");
return null;
}
}
4.9 还是对用户信息封装的类,后面我肯定要替换掉的
package cn.com.dzod.pm.gateway.security.dto;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
@SuppressWarnings("serial")
@Getter
@Setter
public class AuthenticationToken extends UsernamePasswordAuthenticationToken {
private String tenant;
private String host;
public AuthenticationToken(Object principal, Object credentials, String tenant, String host) {
super(principal, credentials);
this.tenant = tenant;
this.host = host;
}
public AuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public AuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
4.10 未认证处理
package cn.com.dzod.pm.gateway.security.exception;
import com.alibaba.fastjson.JSONObject;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset;
@Component
public class AuthEntryPointException implements ServerAuthenticationEntryPoint {
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
return Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(response -> {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBufferFactory dataBufferFactory = response.bufferFactory();
String result = JSONObject.toJSONString("ResultVoUtil.failed(UserStatusCodeEnum.USER_UNAUTHORIZED)");
DataBuffer buffer = dataBufferFactory.wrap(result.getBytes(
Charset.defaultCharset()));
return response.writeWith(Mono.just(buffer));
});
}
}
4.11 登录成功、失败、登出成功处理类就一起粘过来了
package cn.com.dzod.pm.gateway.security.handle;
import cn.com.dzod.pm.gateway.security.dto.MyUserDetailsImpl;
import cn.com.dzod.pm.gateway.util.JwtUtil;
import cn.com.dzod.pm.gateway.util.RedisCache;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Component("jsonServerAuthenticationSuccessHandler")
public class JsonServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
@Autowired
private RedisCache redisCache;
@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
return Mono.defer(() -> Mono.just(webFilterExchange.getExchange().getResponse()).flatMap(response -> {
DataBufferFactory dataBufferFactory = response.bufferFactory();
Object s = authentication.getPrincipal();
MyUserDetailsImpl userDetails = (MyUserDetailsImpl) s;
Map<String, Object> tokenMap = new HashMap<>(2);
tokenMap.put("token", "testToken");
String userId = userDetails.getSysUserDto().getId().toString();
String jwt = JwtUtil.createJWT(userId);
redisCache.setCacheObject("login:"+userId,userDetails);
tokenMap.put("token",jwt);
DataBuffer dataBuffer = dataBufferFactory.wrap(JSON.toJSONString(tokenMap).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}));
}
}
package cn.com.dzod.pm.gateway.security.handle;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class JsonServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
@Override
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
return Mono.defer(() -> Mono.just(webFilterExchange.getExchange()
.getResponse()).flatMap(response -> {
DataBufferFactory dataBufferFactory = response.bufferFactory();
// ResultVO<Map<String, Object>> resultVO = ResultVoUtil.error();
// 账号不存在
if (exception instanceof UsernameNotFoundException) {
// resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_NOT_EXIST);
System.out.println(1);
// 用户名或密码错误
} else if (exception instanceof BadCredentialsException) {
// resultVO = ResultVoUtil.failed(UserStatusCodeEnum.LOGIN_PASSWORD_ERROR);
System.out.println(1);
// 账号已过期
} else if (exception instanceof AccountExpiredException) {
// resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_EXPIRED);
System.out.println(1);
// 账号已被锁定
} else if (exception instanceof LockedException) {
// resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_LOCKED);
System.out.println(1);
// 用户凭证已失效
} else if (exception instanceof CredentialsExpiredException) {
// resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_CREDENTIAL_EXPIRED);
System.out.println(1);
// 账号已被禁用
} else if (exception instanceof DisabledException) {
// resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_DISABLE);
System.out.println(1);
}
DataBuffer dataBuffer = dataBufferFactory.wrap("1234".getBytes());
return response.writeWith(Mono.just(dataBuffer));
}));
}
}
package cn.com.dzod.pm.gateway.security.handle;
import com.alibaba.fastjson.JSONObject;
import io.netty.util.CharsetUtil;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* @Author: 许权
* @Date: 2020/7/10 0010 15:05
*/
@Component
public class JsonServerLogoutSuccessHandler implements ServerLogoutSuccessHandler {
@Override
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
ServerHttpResponse response = exchange.getExchange().getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
String result = JSONObject.toJSONString(("注销成功"));
DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8));
return response.writeWith(Mono.just(buffer));
}
}
总结
到此为止全部代码都拿过来了,除了dubbo相关的模块,那个大家自己替换就好,我做的方法是在gateway当中去鉴权,用户信息通过远程调用去用户服务查询。留一份数据库仅供参考。
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80027 (8.0.27)
Source Host : localhost:3306
Source Schema : auth
Target Server Type : MySQL
Target Server Version : 80027 (8.0.27)
File Encoding : 65001
Date: 20/02/2023 16:02:14
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限编码(标识)',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名称',
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限URL',
`is_delete` int NULL DEFAULT NULL COMMENT '0 未删除 1 已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'home', '首页', '/home/**', NULL);
INSERT INTO `sys_permission` VALUES (2, 'user:add', '添加用户', '/user/add', NULL);
INSERT INTO `sys_permission` VALUES (3, 'user:delete', '删除用户', '/auth/szzb/sys/user/**', NULL);
INSERT INTO `sys_permission` VALUES (4, 'hello:sayHello', '打招呼', '/hello/sayHello', NULL);
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色编码',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
`is_delete` int NULL DEFAULT NULL COMMENT '0 未删除 1 已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'employee', '员工', NULL);
INSERT INTO `sys_role` VALUES (2, 'engineer', '工程师', NULL);
INSERT INTO `sys_role` VALUES (3, 'leader', '组长', NULL);
-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_id` bigint NOT NULL COMMENT '角色ID',
`permission_id` bigint NOT NULL COMMENT '权限ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission` VALUES (2, 2, 1);
INSERT INTO `sys_role_permission` VALUES (3, 2, 2);
INSERT INTO `sys_role_permission` VALUES (4, 3, 1);
INSERT INTO `sys_role_permission` VALUES (5, 3, 2);
INSERT INTO `sys_role_permission` VALUES (6, 3, 3);
INSERT INTO `sys_role_permission` VALUES (7, 3, 4);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`is_delete` int NULL DEFAULT NULL COMMENT '0 未删除 1 已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 366954640605185 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'zhangsan', '$2a$10$e4wFsFHQCNjPe5tTJMPkRuKGwmMGC45pfjMupY9nwbTuoKQ0bKc/u', 0);
INSERT INTO `sys_user` VALUES (366953206145025, '1', '1', 0);
INSERT INTO `sys_user` VALUES (366953206145026, '1', '12345555', 1);
INSERT INTO `sys_user` VALUES (366954640605184, '1', '1', 0);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
INSERT INTO `sys_user_role` VALUES (2, 1, 2);
INSERT INTO `sys_user_role` VALUES (3, 1, 3);
-- ----------------------------
-- Table structure for WORKER_NODE
-- ----------------------------
DROP TABLE IF EXISTS `WORKER_NODE`;
CREATE TABLE `WORKER_NODE` (
`ID` bigint NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
`HOST_NAME` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'host name',
`PORT` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'port',
`TYPE` int NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
`LAUNCH_DATE` date NOT NULL COMMENT 'launch date',
`MODIFIED` timestamp NOT NULL COMMENT 'modified time',
`CREATED` timestamp NOT NULL COMMENT 'created time',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of WORKER_NODE
-- ----------------------------
INSERT INTO `WORKER_NODE` VALUES (1, '192.168.16.6', '1676355147858-17014', 2, '2023-02-14', '2023-02-14 06:12:27', '2023-02-14 06:12:27');
INSERT INTO `WORKER_NODE` VALUES (2, '192.168.16.6', '1676355223677-69932', 2, '2023-02-14', '2023-02-14 06:13:43', '2023-02-14 06:13:43');
INSERT INTO `WORKER_NODE` VALUES (3, '192.168.16.6', '1676355297710-38644', 2, '2023-02-14', '2023-02-14 06:14:57', '2023-02-14 06:14:57');
INSERT INTO `WORKER_NODE` VALUES (4, '192.168.16.6', '1676355532460-82261', 2, '2023-02-14', '2023-02-14 06:18:52', '2023-02-14 06:18:52');
INSERT INTO `WORKER_NODE` VALUES (5, '192.168.16.6', '1676355597540-90161', 2, '2023-02-14', '2023-02-14 06:19:57', '2023-02-14 06:19:57');
INSERT INTO `WORKER_NODE` VALUES (6, '192.168.16.6', '1676355661820-29835', 2, '2023-02-14', '2023-02-14 06:21:01', '2023-02-14 06:21:01');
INSERT INTO `WORKER_NODE` VALUES (7, '192.168.16.6', '1676355912322-89571', 2, '2023-02-14', '2023-02-14 06:25:12', '2023-02-14 06:25:12');
INSERT INTO `WORKER_NODE` VALUES (8, '192.168.16.6', '1676356003676-41259', 2, '2023-02-14', '2023-02-14 06:26:43', '2023-02-14 06:26:43');
INSERT INTO `WORKER_NODE` VALUES (9, '192.168.16.6', '1676356075873-70924', 2, '2023-02-14', '2023-02-14 06:27:55', '2023-02-14 06:27:55');
INSERT INTO `WORKER_NODE` VALUES (10, '192.168.16.6', '1676356223541-13622', 2, '2023-02-14', '2023-02-14 06:30:23', '2023-02-14 06:30:23');
INSERT INTO `WORKER_NODE` VALUES (11, '192.168.16.6', '1676618518856-15854', 2, '2023-02-17', '2023-02-17 07:21:58', '2023-02-17 07:21:58');
INSERT INTO `WORKER_NODE` VALUES (12, '192.168.16.6', '1676618589912-84367', 2, '2023-02-17', '2023-02-17 07:23:09', '2023-02-17 07:23:09');
INSERT INTO `WORKER_NODE` VALUES (13, '192.168.16.6', '1676618655760-76203', 2, '2023-02-17', '2023-02-17 07:24:15', '2023-02-17 07:24:15');
INSERT INTO `WORKER_NODE` VALUES (14, '192.168.16.6', '1676622061020-67338', 2, '2023-02-17', '2023-02-17 08:21:01', '2023-02-17 08:21:01');
INSERT INTO `WORKER_NODE` VALUES (15, '192.168.1.42', '1676813354530-47481', 2, '2023-02-19', '2023-02-19 13:29:14', '2023-02-19 13:29:14');
SET FOREIGN_KEY_CHECKS = 1;