1、简介
Spring Security 是 Spring 家族中的一个安全管理框架。相比于另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。
参考文档:https://juejin.cn/post/7106300827035238407
一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。
一般Web应用的需要进行认证和授权。
认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权:经过认证后判断当前用户是否有权限进行某个操作
而认证和授权也是SpringSecurity作为安全框架的核心功能。
2、搭建SpringSecurity项目
2.1、pom文件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.8</version>
</dependency>
<!-- Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
</dependencies>
2.2、SpringSecurity完整流程
SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。
图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要由它负责。
**ExceptionTranslationFilter:**处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。
**FilterSecurityInterceptor:**负责权限校验的过滤器。
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
AuthenticationManager接口:定义了认证Authentication的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
3、自定义登录流程
3.1、自定义登录流程分析(方式一)
1、先请求初始化验证码接口获取验证码唯一标识 (生成验证码唯一标识,并存入redis,key=唯一标识,value=验证码信息,ex=过期时间)
2、通过获取的唯一标识请求根据验证码ID获取图片接口,根据唯一标识从redis取出验证码信息,转换为流输出到页面
3、用户调用登录接口进行登录
WebSecurityConfig(Security 核心配置类) ImageValidateFilter(图形验证码过滤器) UserDetailsServiceImpl(获取用户信息) AuthenticationSuccessHandler(登录成功处理类) AuthenticationFailHandler(登录失败出来类) JWTAuthenticationFilter(JWT过滤器)
1)先经过图形验证码过滤器,根据配置文件配置的url地址先判断该请求是否需求验证验证码,如果不需要验证则直接放行,否则获取验证码的唯一标识和验证码,判断是否为空,判断验证码是否过期,判断验证码是否正确,如果验证成功则删除redis并放行。
2)WebSecurityConfig配置了UserDetailsServiceImpl为认证处理类,根据用户名判断用户登录错误次数是否超过限制,获取用户信息,封装为SecurityUserDetails(重写getAuthorities等方法)
3)认证成功则走AuthenticationSuccessHandler登录成功处理类,获取参数判断用户选择是否选择保持登录,如果保持登录则获取配置文件信息判断是否使用redis存储,如果使用redis存储则生成token,判断是否缓存权限信息,判断是否使用单设备登录,保存登录信息到redis,如果不适应redi存储则默认使用jwt
4)其余接口访问JWTAuthenticationFilterJWT过滤器,判断请求头是否有accessToken,如果有则通过redis取出用户信息并设置到上下文中
3.2、自定义登录流程分析(方式二)
1、请求登录接口
1)对获取的密码进行解密,根据验证码id查询redis验证码,清除redis中验证码,判断验证码是否匹配
2)执行UsernamePasswordAuthenticationToken传递用户名和密码会自动调用UserDetailsService的实现类
2.1)判断配置是否开启用户信息缓存,ConcurrentHashMap中是否存在当前的登录用户,如果存在则从map中取出并返回JwtUserDto对象
2.2)如果没有信息缓存,则从数据库查询用户信息,组装用户权限信息生成JwtUserDto对象,将信息存入ConcurrentHashMap并返回JwtUserDto对象
3)获取认证和权限信息并
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
根据authentication创建一个token,保存在线信息(根据request获取ip、浏览器等信息并和用户名等信息封装为OnlineUserDto对象)存储至redis,key = "online-token-"+token,默认保存4小时
4)组装map对象存储token以及用户名
5)判断是否开启单用户登录(用户名,igoreToken)
5.1)获取所有redis在线信息,过滤出包含用户名的redis信息并返回list
5.2)判断list是否为空,为空则返回
5.3)遍历list对象,查找用户名相等的对象,取出对象中的token并解密
5.4)如果igoreToken不为空且igoreToken不等于token则删除token(踢出用户) 或者 igoreToken为空则也删除token
2、过滤器TokenFilter
1)根据request获取token,判断token是否为空,为空则放行
2)根据token取出redis中的OnlineUserDto对象,判断OnlineUserDto是否为空,为空则删除ConcurrentHashMap中的用户信息缓存
3)依据Token 获取鉴权信息
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
4、代码实现(基于方式一)
Security 核心配置类(核心)
@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private XbootTokenProperties tokenProperties;
@Autowired
private IgnoredUrlsProperties ignoredUrlsProperties;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private AuthenticationSuccessHandler successHandler;
@Autowired
private AuthenticationFailHandler failHandler;
@Autowired
private RestAccessDeniedHandler accessDeniedHandler;
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
@Autowired
private ImageValidateFilter imageValidateFilter;
@Autowired
private RedisTemplateHelper redisTemplate;
@Autowired
private SecurityUtil securityUtil;
//创建全局AuthenticationManager 默认找当前项目中是否存在自定义 UserDetailService 实例 并设置密码验证方式
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
// 除配置文件忽略路径其它所有请求都需经过认证和授权
for (String url : ignoredUrlsProperties.getUrls()) {
registry.antMatchers(url).permitAll();
}
registry.and()
// 表单登录方式
.formLogin()
//登录页面地址
.loginPage("/common/needLogin")
// 登录请求url
.loginProcessingUrl("/login")
//直接放行
.permitAll()
// 成功处理类
.successHandler(successHandler)
// 失败
.failureHandler(failHandler)
.and()
// 允许网页iframe
.headers().frameOptions().disable()
.and()
.logout()
.permitAll()
.and()
.authorizeRequests()
// 任何请求
.anyRequest()
// 需要身份认证
.authenticated()
.and()
// 允许跨域
.cors().and()
// 关闭跨站请求防护
.csrf().disable()
// 前后端分离采用JWT 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 自定义权限拒绝处理类
.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.and()
// 添加自定义权限过滤器
.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)
// 图形验证码过滤器
.addFilterBefore(imageValidateFilter, UsernamePasswordAuthenticationFilter.class)
// 添加JWT过滤器 除已配置的其它请求都需经过此过滤器
.addFilter(new JWTAuthenticationFilter(authenticationManager(), tokenProperties, redisTemplate, securityUtil));
}
}
自定义UserDetailService实例(核心)
@Slf4j
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private RedisTemplateHelper redisTemplate;
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String flagKey = "loginFailFlag:" + username;
String value = redisTemplate.get(flagKey);
Long timeRest = redisTemplate.getExpire(flagKey, TimeUnit.MINUTES);
if (StrUtil.isNotBlank(value)) {
// 超过限制次数
throw new LoginFailLimitException("登录错误次数超过限制,请" + timeRest + "分钟后再试");
}
User user = userService.findByUsername(username);
return new SecurityUserDetails(user);
}
}
@Slf4j
public class SecurityUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
private List<PermissionDTO> permissions;
private List<RoleDTO> roles;
public SecurityUserDetails(User user) {
if (user != null) {
// Principal用户信息
this.setUsername(user.getUsername());
this.setPassword(user.getPassword());
this.setStatus(user.getStatus());
this.permissions = user.getPermissions();
this.roles = user.getRoles();
}
}
/**
* 添加用户拥有的权限和角色
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorityList = new ArrayList<>();
// 添加请求权限
if (permissions != null && permissions.size() > 0) {
for (PermissionDTO permission : permissions) {
if (StrUtil.isNotBlank(permission.getTitle()) && StrUtil.isNotBlank(permission.getPath())) {
authorityList.add(new SimpleGrantedAuthority(permission.getTitle()));
}
}
}
// 添加角色
if (roles != null && roles.size() > 0) {
// lambda表达式
roles.forEach(item -> {
if (StrUtil.isNotBlank(item.getName())) {
authorityList.add(new SimpleGrantedAuthority(item.getName()));
}
});
}
return authorityList;
}
/**
* 账户是否过期
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 是否禁用
* @return
*/
@Override
public boolean isAccountNonLocked() {
return CommonConstant.USER_STATUS_LOCK.equals(this.getStatus()) ? false : true;
}
/**
* 密码是否过期
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否启用
* @return
*/
@Override
public boolean isEnabled() {
return CommonConstant.USER_STATUS_NORMAL.equals(this.getStatus()) ? true : false;
}
}
登录成功处理类
@Slf4j
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private XbootTokenProperties tokenProperties;
@Autowired
private IpInfoUtil ipInfoUtil;
@Autowired
private RedisTemplateHelper redisTemplate;
@Override
@SystemLog(description = "登录系统", type = LogType.LOGIN)
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 用户选择保存登录状态几天(记住我)
String saveLogin = request.getParameter(SecurityConstant.SAVE_LOGIN);
Boolean saved = false;
if (StrUtil.isNotBlank(saveLogin) && Boolean.valueOf(saveLogin)) {
saved = true;
if (!tokenProperties.getRedis()) {
tokenProperties.setTokenExpireTime(tokenProperties.getSaveLoginTime() * 60 * 24);
}
}
String username = ((UserDetails) authentication.getPrincipal()).getUsername();
List<GrantedAuthority> authorities = (List<GrantedAuthority>) ((UserDetails) authentication.getPrincipal()).getAuthorities();
List<String> list = new ArrayList<>();
for (GrantedAuthority g : authorities) {
list.add(g.getAuthority());
}
ipInfoUtil.getUrl(request);
// 登陆成功生成token
String token;
if (tokenProperties.getRedis()) {
// redis
token = IdUtil.simpleUUID();
TokenUser user = new TokenUser(username, list, saved);
// 不缓存权限
if (!tokenProperties.getStorePerms()) {
user.setPermissions(null);
}
// 单设备登录 之前的token失效
if (tokenProperties.getSdl()) {
String oldToken = redisTemplate.get(SecurityConstant.USER_TOKEN + username);
if (StrUtil.isNotBlank(oldToken)) {
redisTemplate.delete(SecurityConstant.TOKEN_PRE + oldToken);
}
}
if (saved) {
redisTemplate.set(SecurityConstant.USER_TOKEN + username, token, tokenProperties.getSaveLoginTime(), TimeUnit.DAYS);
redisTemplate.set(SecurityConstant.TOKEN_PRE + token, new Gson().toJson(user), tokenProperties.getSaveLoginTime(), TimeUnit.DAYS);
} else {
redisTemplate.set(SecurityConstant.USER_TOKEN + username, token, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
redisTemplate.set(SecurityConstant.TOKEN_PRE + token, new Gson().toJson(user), tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
}
} else {
// JWT不缓存权限 避免JWT长度过长
list = null;
// JWT
token = SecurityConstant.TOKEN_SPLIT + Jwts.builder()
// 主题 放入用户名
.setSubject(username)
// 自定义属性 放入用户拥有请求权限
.claim(SecurityConstant.AUTHORITIES, new Gson().toJson(list))
// 失效时间
.setExpiration(new Date(System.currentTimeMillis() + tokenProperties.getTokenExpireTime() * 60 * 1000))
// 签名算法和密钥
.signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
.compact();
}
ResponseUtil.out(response, ResponseUtil.resultMap(true, 200, "登录成功", token));
}
}
登录失败处理类
@Slf4j
@Component
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private XbootTokenProperties tokenProperties;
@Autowired
private RedisTemplateHelper redisTemplate;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
String username = request.getParameter("username");
recordLoginTime(username);
String key = "loginTimeLimit:" + username;
String value = redisTemplate.get(key);
if (StrUtil.isBlank(value)) {
value = "0";
}
//获取已登录错误次数
int loginFailTime = Integer.parseInt(value);
int restLoginTime = tokenProperties.getLoginTimeLimit() - loginFailTime;
log.info("用户" + username + "登录失败,还有" + restLoginTime + "次机会");
if (restLoginTime <= 3 && restLoginTime > 0) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "用户名或密码错误,还有" + restLoginTime + "次尝试机会"));
} else if (restLoginTime <= 0) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "登录错误次数超过限制,请" + tokenProperties.getLoginAfterTime() + "分钟后再试"));
} else {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "用户名或密码错误"));
}
} else if (e instanceof DisabledException) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "账户被禁用,请联系管理员"));
} else if (e instanceof LoginFailLimitException) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, ((LoginFailLimitException) e).getMsg()));
} else {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "登录失败,其他内部错误"));
}
}
/**
* 判断用户登陆错误次数
*/
public boolean recordLoginTime(String username) {
String key = "loginTimeLimit:" + username;
String flagKey = "loginFailFlag:" + username;
String value = redisTemplate.get(key);
if (StrUtil.isBlank(value)) {
value = "0";
}
// 获取已登录错误次数
Integer loginFailTime = Integer.parseInt(value) + 1;
redisTemplate.set(key, loginFailTime.toString(), tokenProperties.getLoginAfterTime(), TimeUnit.MINUTES);
if (loginFailTime >= tokenProperties.getLoginTimeLimit()) {
redisTemplate.set(flagKey, "fail", tokenProperties.getLoginAfterTime(), TimeUnit.MINUTES);
return false;
}
return true;
}
}
权限不足处理类
@Component
@Slf4j
public class RestAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
throws IOException, ServletException {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 403, "抱歉,您没有访问权限"));
}
}
自定义权限过滤器
@Slf4j
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
图形验证码过滤器
@Slf4j
@Configuration
public class ImageValidateFilter extends OncePerRequestFilter {
@Autowired
private CaptchaProperties captchaProperties;
@Autowired
private RedisTemplateHelper redisTemplate;
@Autowired
private PathMatcher pathMatcher;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 判断URL是否需要验证
Boolean flag = false;
String requestUrl = request.getRequestURI();
for (String url : captchaProperties.getImage()) {
if (pathMatcher.match(url, requestUrl)) {
flag = true;
break;
}
}
if (flag) {
String captchaId = request.getParameter("captchaId");
String code = request.getParameter("code");
if (StrUtil.isBlank(captchaId) || StrUtil.isBlank(code)) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "请传入图形验证码所需参数captchaId或code"));
return;
}
String redisCode = redisTemplate.get(captchaId);
if (StrUtil.isBlank(redisCode)) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "验证码已过期,请重新获取"));
return;
}
if (!redisCode.toLowerCase().equals(code.toLowerCase())) {
log.info("验证码错误:code:" + code + ",redisCode:" + redisCode);
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "图形验证码输入错误"));
return;
}
// 已验证清除key
redisTemplate.delete(captchaId);
// 验证成功 放行
chain.doFilter(request, response);
return;
}
// 无需验证 放行
chain.doFilter(request, response);
}
}
JWT过滤器(重点)
处理 HTTP 请求的 BASIC 授权标头,将结果放入SecurityContextHolder 。
如果身份验证成功,生成的Authentication对象将被放入SecurityContextHolder中。
如果身份验证失败并且ignoreFailure为false (默认值),则调用AuthenticationEntryPoint实现(除非ignoreFailure属性设置为true )。通常这应该是BasicAuthenticationEntryPoint ,它将提示用户通过 BASIC 身份验证再次进行身份验证。
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
private XbootTokenProperties tokenProperties;
private RedisTemplateHelper redisTemplate;
private SecurityUtil securityUtil;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager,
XbootTokenProperties tokenProperties,
RedisTemplateHelper redisTemplate, SecurityUtil securityUtil) {
super(authenticationManager);
this.tokenProperties = tokenProperties;
this.redisTemplate = redisTemplate;
this.securityUtil = securityUtil;
}
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// System.out.println(request.getRequestURI()); /xboot/admin/instances
// System.out.println(request.getRequestURL().toString()); http://127.0.0.1:8888/xboot/admin/instances
String header = request.getHeader(SecurityConstant.HEADER);
if (StrUtil.isBlank(header)) {
header = request.getParameter(SecurityConstant.HEADER);
}
Boolean notValid = StrUtil.isBlank(header) || (!tokenProperties.getRedis() && !header.startsWith(SecurityConstant.TOKEN_SPLIT));
if (notValid) {
chain.doFilter(request, response);
return;
}
try {
// UsernamePasswordAuthenticationToken 继承 AbstractAuthenticationToken 实现 Authentication
// 所以当在页面中输入用户名和密码之后首先会进入到 UsernamePasswordAuthenticationToken验证(Authentication),
UsernamePasswordAuthenticationToken authentication = null;
if (StrUtil.isNotBlank(header)) {
authentication = getAuthentication(header, response);
}
if (authentication == null) {
return;
}
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.warn(e.toString());
}
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(String header, HttpServletResponse response) {
// 用户名
String username = null;
// 权限
List<GrantedAuthority> authorities = new ArrayList<>();
if (tokenProperties.getRedis()) {
// redis
String v = redisTemplate.get(SecurityConstant.TOKEN_PRE + header);
if (StrUtil.isBlank(v)) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 401, "登录已失效,请重新登录"));
return null;
}
TokenUser user = new Gson().fromJson(v, TokenUser.class);
username = user.getUsername();
if (tokenProperties.getStorePerms()) {
// 缓存了权限
for (String ga : user.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(ga));
}
} else {
// 未缓存 读取权限数据
authorities = securityUtil.getCurrUserPerms(username);
}
if (!user.getSaveLogin()) {
// 若未保存登录状态重新设置失效时间
redisTemplate.set(SecurityConstant.USER_TOKEN + username, header, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
redisTemplate.set(SecurityConstant.TOKEN_PRE + header, v, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
}
} else {
// JWT
try {
// 解析token
Claims claims = Jwts.parser()
.setSigningKey(SecurityConstant.JWT_SIGN_KEY)
.parseClaimsJws(header.replace(SecurityConstant.TOKEN_SPLIT, ""))
.getBody();
// 获取用户名
username = claims.getSubject();
// JWT不缓存权限 读取权限数据 避免JWT长度过长
authorities = securityUtil.getCurrUserPerms(username);
} catch (ExpiredJwtException e) {
ResponseUtil.out(response, ResponseUtil.resultMap(false, 401, "登录已失效,请重新登录"));
} catch (Exception e) {
log.error(e.toString());
ResponseUtil.out(response, ResponseUtil.resultMap(false, 500, "解析token错误"));
}
}
if (StrUtil.isNotBlank(username)) {
// 踩坑提醒 此处password不能为null
User principal = new User(username, "", authorities);
return new UsernamePasswordAuthenticationToken(principal, null, authorities);
}
return null;
}
}
RedisTemplateHelper
@Component
public class RedisTemplateHelper {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* scan实现
* @param pattern 表达式
* @param consumer 对迭代到的key进行操作
*/
private void scan(String pattern, Consumer<byte[]> consumer) {
redisTemplate.execute((RedisConnection connection) -> {
try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count(Long.MAX_VALUE).match(pattern).build())) {
cursor.forEachRemaining(consumer);
return null;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
/**
* scan获取符合条件的key
* @param pattern 表达式
* @return
*/
public Set<String> scan(String pattern) {
Set<String> keys = new HashSet<>();
this.scan(pattern, item -> {
// 符合条件的key
String key = new String(item, StandardCharsets.UTF_8);
keys.add(key);
});
return keys;
}
/**
* 通过通配符表达式删除所有
* @param pattern
*/
public void deleteByPattern(String pattern) {
Set<String> keys = this.scan(pattern);
redisTemplate.delete(keys);
}
/** -------------------key相关操作--------------------- */
/**
* 删除key
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 批量删除key
* @param keys
*/
public void delete(Collection<String> keys) {
redisTemplate.delete(keys);
}
/**
* 序列化key
* @param key
* @return
*/
public byte[] dump(String key) {
return redisTemplate.dump(key);
}
/**
* 是否存在key
* @param key
* @return
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
* @param key
* @param timeout
* @param unit 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
* @return
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 设置过期时间
* @param key
* @param date
* @return
*/
public Boolean expireAt(String key, Date date) {
return redisTemplate.expireAt(key, date);
}
/**
* 查找匹配的key
* @param pattern
* @return
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 将当前数据库的 key 移动到给定的数据库 db 当中
* @param key
* @param dbIndex
* @return
*/
public Boolean move(String key, int dbIndex) {
return redisTemplate.move(key, dbIndex);
}
/**
* 移除 key 的过期时间,key 将持久保持
* @param key
* @return
*/
public Boolean persist(String key) {
return redisTemplate.persist(key);
}
/**
* 返回 key 的剩余的过期时间
* @param key
* @param unit
* @return
*/
public Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
/**
* 返回 key 的剩余的过期时间
* @param key
* @return
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
/**
* 从当前数据库中随机返回一个 key
* @return
*/
public String randomKey() {
return redisTemplate.randomKey();
}
/**
* 修改 key 的名称
* @param oldKey
* @param newKey
*/
public void rename(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
/**
* 仅当 newkey 不存在时,将 oldKey 改名为 newkey
* @param oldKey
* @param newKey
* @return
*/
public Boolean renameIfAbsent(String oldKey, String newKey) {
return redisTemplate.renameIfAbsent(oldKey, newKey);
}
/**
* 返回 key 所储存的值的类型
* @param key
* @return
*/
public DataType type(String key) {
return redisTemplate.type(key);
}
/** -------------------string相关操作--------------------- */
/**
* 设置指定 key 的值
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
* @param key
* @param value
* @param timeout 过期时间
* @param unit 时间单位
*/
public void set(String key, String value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 获取指定 key 的值
* @param key
* @return
*/
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 返回 key 中字符串值的子字符
* @param key
* @param start
* @param end
* @return
*/
public String getRange(String key, long start, long end) {
return redisTemplate.opsForValue().get(key, start, end);
}
/**
* 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
* @param key
* @param value
* @return
*/
public String getAndSet(String key, String value) {
return redisTemplate.opsForValue().getAndSet(key, value);
}
/**
* 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
* @param key
* @param offset
* @return
*/
public Boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 批量获取
* @param keys
* @return
*/
public List<String> multiGet(Collection<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
* @param key
* @param offset 位置
* @param value 值,true为1, false为0
* @return
*/
public boolean setBit(String key, long offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* 只有在 key 不存在时设置 key 的值
* @param key
* @param value
* @return 之前已经存在返回false, 不存在返回true
*/
public boolean setIfAbsent(String key, String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
* @param key
* @param value
* @param offset 从指定位置开始覆写
*/
public void setRange(String key, String value, long offset) {
redisTemplate.opsForValue().set(key, value, offset);
}
/**
* 获取字符串的长度
* @param key
* @return
*/
public Long size(String key) {
return redisTemplate.opsForValue().size(key);
}
/**
* 批量添加
* @param maps
*/
public void multiSet(Map<String, String> maps) {
redisTemplate.opsForValue().multiSet(maps);
}
/**
* 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
* @param maps
* @return 之前已经存在返回false, 不存在返回true
*/
public boolean multiSetIfAbsent(Map<String, String> maps) {
return redisTemplate.opsForValue().multiSetIfAbsent(maps);
}
/**
* 增加(自增长), 负数则为自减
* @param key
* @param increment
* @return
*/
public Long incrBy(String key, long increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
* 增加(自增长)
* @param key
* @param increment
* @return
*/
public Double incrByFloat(String key, double increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
* 追加到末尾
* @param key
* @param value
* @return
*/
public Integer append(String key, String value) {
return redisTemplate.opsForValue().append(key, value);
}
/** -------------------hash相关操作------------------------- */
/**
* 获取存储在哈希表中指定字段的值
* @param key
* @param field
* @return
*/
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 获取所有给定字段的值
* @param key
* @return
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 获取所有给定字段的值
* @param key
* @param fields
* @return
*/
public List<Object> hMultiGet(String key, Collection<Object> fields) {
return redisTemplate.opsForHash().multiGet(key, fields);
}
/**
* 添加单个
* @param key
* @param hashKey
* @param value
*/
public void hPut(String key, String hashKey, String value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
/**
* 添加集合
* @param key
* @param maps
*/
public void hPutAll(String key, Map<String, String> maps) {
redisTemplate.opsForHash().putAll(key, maps);
}
/**
* 仅当hashKey不存在时才设置
* @param key
* @param hashKey
* @param value
* @return
*/
public Boolean hPutIfAbsent(String key, String hashKey, String value) {
return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}
/**
* 删除一个或多个哈希表字段
* @param key
* @param fields
* @return
*/
public Long hDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* 查看哈希表 key 中,指定的字段是否存在
* @param key
* @param field
* @return
*/
public boolean hExists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
* @param key
* @param field
* @param increment
* @return
*/
public Long hIncrBy(String key, Object field, long increment) {
return redisTemplate.opsForHash().increment(key, field, increment);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
* @param key
* @param field
* @param delta
* @return
*/
public Double hIncrByFloat(String key, Object field, double delta) {
return redisTemplate.opsForHash().increment(key, field, delta);
}
/**
* 获取所有哈希表中的字段
* @param key
* @return
*/
public Set<Object> hKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 获取哈希表中字段的数量
* @param key
* @return
*/
public Long hSize(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 获取哈希表中所有值
* @param key
* @return
*/
public List<Object> hValues(String key) {
return redisTemplate.opsForHash().values(key);
}
/**
* 迭代哈希表中的键值对
* @param key
* @param options
* @return
*/
public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
return redisTemplate.opsForHash().scan(key, options);
}
/** ------------------------list相关操作---------------------------- */
/**
* 通过索引获取列表中的元素
* @param key
* @param index
* @return
*/
public String lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
/**
* 获取列表指定范围内的元素
* @param key
* @param start 开始位置, 0是开始位置
* @param end 结束位置, -1返回所有
* @return
*/
public List<String> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 存储在list头部
* @param key
* @param value
* @return
*/
public Long lLeftPush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, String... value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
* 当list存在的时候才加入
* @param key
* @param value
* @return
*/
public Long lLeftPushIfPresent(String key, String value) {
return redisTemplate.opsForList().leftPushIfPresent(key, value);
}
/**
* 如果pivot存在,再pivot前面添加
* @param key
* @param pivot
* @param value
* @return
*/
public Long lLeftPush(String key, String pivot, String value) {
return redisTemplate.opsForList().leftPush(key, pivot, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, String... value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
* 为已存在的列表添加值
* @param key
* @param value
* @return
*/
public Long lRightPushIfPresent(String key, String value) {
return redisTemplate.opsForList().rightPushIfPresent(key, value);
}
/**
* 在pivot元素的右边添加值
* @param key
* @param pivot
* @param value
* @return
*/
public Long lRightPush(String key, String pivot, String value) {
return redisTemplate.opsForList().rightPush(key, pivot, value);
}
/**
* 通过索引设置列表元素的值
* @param key
* @param index 位置
* @param value
*/
public void lSet(String key, long index, String value) {
redisTemplate.opsForList().set(key, index, value);
}
/**
* 移出并获取列表的第一个元素
* @param key
* @return 删除的元素
*/
public String lLeftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
* @param key
* @param timeout 等待时间
* @param unit 时间单位
* @return
*/
public String lBLeftPop(String key, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().leftPop(key, timeout, unit);
}
/**
* 移除并获取列表最后一个元素
* @param key
* @return 删除的元素
*/
public String lRightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
* @param key
* @param timeout 等待时间
* @param unit 时间单位
* @return
*/
public String lBRightPop(String key, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPop(key, timeout, unit);
}
/**
* 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
* @param sourceKey
* @param destinationKey
* @return
*/
public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey);
}
/**
* 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
* @param sourceKey
* @param destinationKey
* @param timeout
* @param unit
* @return
*/
public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey, timeout, unit);
}
/**
* 删除集合中值等于value得元素
* @param key
* @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;index<0, 从尾部开始删除第一个值等于value的元素;
* @param value
* @return
*/
public Long lRemove(String key, long index, String value) {
return redisTemplate.opsForList().remove(key, index, value);
}
/**
* 裁剪list
* @param key
* @param start
* @param end
*/
public void lTrim(String key, long start, long end) {
redisTemplate.opsForList().trim(key, start, end);
}
/**
* 获取列表长度
* @param key
* @return
*/
public Long lLen(String key) {
return redisTemplate.opsForList().size(key);
}
/** --------------------set相关操作-------------------------- */
/**
* set添加元素
* @param key
* @param values
* @return
*/
public Long sAdd(String key, String... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* set移除元素
* @param key
* @param values
* @return
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
/**
* 移除并返回集合的一个随机元素
* @param key
* @return
*/
public String sPop(String key) {
return redisTemplate.opsForSet().pop(key);
}
/**
* 将元素value从一个集合移到另一个集合
* @param key
* @param value
* @param destKey
* @return
*/
public Boolean sMove(String key, String value, String destKey) {
return redisTemplate.opsForSet().move(key, value, destKey);
}
/**
* 获取集合的大小
* @param key
* @return
*/
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* 判断集合是否包含value
* @param key
* @param value
* @return
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 获取两个集合的交集
* @param key
* @param otherKey
* @return
*/
public Set<String> sIntersect(String key, String otherKey) {
return redisTemplate.opsForSet().intersect(key, otherKey);
}
/**
* 获取key集合与多个集合的交集
* @param key
* @param otherKeys
* @return
*/
public Set<String> sIntersect(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().intersect(key, otherKeys);
}
/**
* key集合与otherKey集合的交集存储到destKey集合中
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey);
}
/**
* key集合与多个集合的交集存储到destKey集合中
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey);
}
/**
* 获取两个集合的并集
* @param key
* @param otherKeys
* @return
*/
public Set<String> sUnion(String key, String otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* 获取key集合与多个集合的并集
* @param key
* @param otherKeys
* @return
*/
public Set<String> sUnion(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* key集合与otherKey集合的并集存储到destKey中
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
}
/**
* key集合与多个集合的并集存储到destKey中
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 获取两个集合的差集
* @param key
* @param otherKey
* @return
*/
public Set<String> sDifference(String key, String otherKey) {
return redisTemplate.opsForSet().difference(key, otherKey);
}
/**
* 获取key集合与多个集合的差集
* @param key
* @param otherKeys
* @return
*/
public Set<String> sDifference(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().difference(key, otherKeys);
}
/**
* key集合与otherKey集合的差集存储到destKey中
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sDifference(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey);
}
/**
* key集合与多个集合的差集存储到destKey中
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sDifference(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
destKey);
}
/**
* 返回集合指定元素
* @param key
* @return
*/
public Set<String> setMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 随机获取集合中的一个元素
* @param key
* @return
*/
public String sRandomMember(String key) {
return redisTemplate.opsForSet().randomMember(key);
}
/**
* 随机获取集合中count个元素
* @param key
* @param count
* @return
*/
public List<String> sRandomMembers(String key, long count) {
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* 随机获取集合中count个元素并且去除重复的
* @param key
* @param count
* @return
*/
public Set<String> sDistinctRandomMembers(String key, long count) {
return redisTemplate.opsForSet().distinctRandomMembers(key, count);
}
/**
* scan扫描返回指定key
* @param key
* @param options
* @return
*/
public Cursor<String> sScan(String key, ScanOptions options) {
return redisTemplate.opsForSet().scan(key, options);
}
/**------------------zSet相关操作--------------------------------*/
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
* @param key
* @param value
* @param score
* @return
*/
public Boolean zAdd(String key, String value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* 添加集合
* @param key
* @param values
* @return
*/
public Long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> values) {
return redisTemplate.opsForZSet().add(key, values);
}
/**
* 移除
* @param key
* @param values
* @return
*/
public Long zRemove(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key, values);
}
/**
* 增加元素的score值,并返回增加后的值
* @param key
* @param value
* @param delta
* @return
*/
public Double zIncrementScore(String key, String value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
* @param key
* @param value
* @return 0表示第一位
*/
public Long zRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
/**
* 返回元素在集合的排名,按元素的score值由大到小排列
* @param key
* @param value
* @return
*/
public Long zReverseRank(String key, Object value) {
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**
* 获取集合的元素, 从小到大排序
* @param key
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return
*/
public Set<String> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 获取集合元素, 并且把score值也获取
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start,
long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
/**
* 根据Score值查询集合元素
* @param key
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<String> zRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* 根据Score值查询集合元素, 从小到大排序
* @param key
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}
/**
* 根据Score值和指定位置查询集合元素, 从小到大排序
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max, long start, long end) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, start, end);
}
/**
* 获取集合的元素, 从大到小排序
* @param key
* @param start
* @param end
* @return
*/
public Set<String> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 获取集合的元素, 从大到小排序, 并返回score值
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key,
long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}
/**
* 根据Score值查询集合元素, 从大到小排序
* @param key
* @param min
* @param max
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**
* 根据Score值查询集合元素, 从大到小排序
* @param key
* @param min
* @param max
* @return
*/
public Set<ZSetOperations.TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max);
}
/**
* 根据Score值和指定位置查询集合元素, 从大到小排序
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min, double max, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end);
}
/**
* 根据score值获取集合元素数量
* @param key
* @param min
* @param max
* @return
*/
public Long zCount(String key, double min, double max) {
return redisTemplate.opsForZSet().count(key, min, max);
}
/**
* 获取集合大小
* @param key
* @return
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 获取集合大小
* @param key
* @return
*/
public Long zZCard(String key) {
return redisTemplate.opsForZSet().zCard(key);
}
/**
* 获取集合中value元素的score值
* @param key
* @param value
* @return
*/
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
/**
* 移除指定索引位置的成员
* @param key
* @param start
* @param end
* @return
*/
public Long zRemoveRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**
* 根据指定的score值的范围来移除成员
* @param key
* @param min
* @param max
* @return
*/
public Long zRemoveRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
/**
* 获取key和otherKey的并集并存储在destKey中
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
}
/**
* 获取key和otherKey的并集并存储在destKey中
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, Collection<String> otherKeys, String destKey) {
return redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 交集
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey);
}
/**
* 交集
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, Collection<String> otherKeys, String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey);
}
/**
* scan扫描指定key
* @param key
* @param options
* @return
*/
public Cursor<ZSetOperations.TypedTuple<String>> zScan(String key, ScanOptions options) {
return redisTemplate.opsForZSet().scan(key, options);
}
/**------------------其他操作--------------------------------*/
/**
* 获得连接工厂
* @return
*/
public RedisConnectionFactory getConnectionFactory() {
return redisTemplate.getConnectionFactory();
}
}
@Component
public class SecurityUtil {
@Autowired
private XbootTokenProperties tokenProperties;
@Autowired
private UserService userService;
@Autowired
private IUserRoleService iUserRoleService;
@Autowired
private DepartmentService departmentService;
@Autowired
private RedisTemplateHelper redisTemplate;
public String getToken(String username, Boolean saveLogin) {
if (StrUtil.isBlank(username)) {
throw new XbootException("username不能为空");
}
Boolean saved = false;
if (saveLogin == null || saveLogin) {
saved = true;
if (!tokenProperties.getRedis()) {
tokenProperties.setTokenExpireTime(tokenProperties.getSaveLoginTime() * 60 * 24);
}
}
// 生成token
User u = userService.findByUsername(username);
List<String> list = new ArrayList<>();
// 缓存权限
if (tokenProperties.getStorePerms()) {
for (PermissionDTO p : u.getPermissions()) {
if (StrUtil.isNotBlank(p.getTitle()) && StrUtil.isNotBlank(p.getPath())) {
list.add(p.getTitle());
}
}
for (RoleDTO r : u.getRoles()) {
list.add(r.getName());
}
}
// 登陆成功生成token
String token;
if (tokenProperties.getRedis()) {
// redis
token = IdUtil.simpleUUID();
TokenUser user = new TokenUser(u.getUsername(), list, saved);
// 单设备登录 之前的token失效
if (tokenProperties.getSdl()) {
String oldToken = redisTemplate.get(SecurityConstant.USER_TOKEN + u.getUsername());
if (StrUtil.isNotBlank(oldToken)) {
redisTemplate.delete(SecurityConstant.TOKEN_PRE + oldToken);
}
}
if (saved) {
redisTemplate.set(SecurityConstant.USER_TOKEN + u.getUsername(), token, tokenProperties.getSaveLoginTime(), TimeUnit.DAYS);
redisTemplate.set(SecurityConstant.TOKEN_PRE + token, new Gson().toJson(user), tokenProperties.getSaveLoginTime(), TimeUnit.DAYS);
} else {
redisTemplate.set(SecurityConstant.USER_TOKEN + u.getUsername(), token, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
redisTemplate.set(SecurityConstant.TOKEN_PRE + token, new Gson().toJson(user), tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
}
} else {
// JWT不缓存权限 避免JWT长度过长
list = null;
// JWT
token = SecurityConstant.TOKEN_SPLIT + Jwts.builder()
//主题 放入用户名
.setSubject(u.getUsername())
//自定义属性 放入用户拥有请求权限
.claim(SecurityConstant.AUTHORITIES, new Gson().toJson(list))
//失效时间
.setExpiration(new Date(System.currentTimeMillis() + tokenProperties.getTokenExpireTime() * 60 * 1000))
//签名算法和密钥
.signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
.compact();
}
return token;
}
/**
* 获取当前登录用户
* @return
*/
public User getCurrUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated() || authentication.getName() == null
|| authentication instanceof AnonymousAuthenticationToken) {
throw new XbootException("未检测到登录用户");
}
return userService.findByUsername(authentication.getName());
}
/**
* 获取当前用户数据权限 null代表具有所有权限 包含值为-1的数据代表无任何权限
*/
public List<String> getDeparmentIds() {
List<String> deparmentIds = new ArrayList<>();
User u = getCurrUser();
// 读取缓存
String key = "userRole::depIds:" + u.getId();
String v = redisTemplate.get(key);
if (StrUtil.isNotBlank(v)) {
deparmentIds = new Gson().fromJson(v, new TypeToken<List<String>>() {
}.getType());
return deparmentIds;
}
// 当前用户拥有角色
List<Role> roles = iUserRoleService.findByUserId(u.getId());
// 判断有无全部数据的角色
Boolean flagAll = false;
for (Role r : roles) {
if (r.getDataType() == null || r.getDataType().equals(CommonConstant.DATA_TYPE_ALL)) {
flagAll = true;
break;
}
}
// 包含全部权限返回null
if (flagAll) {
return null;
}
// 每个角色判断 求并集
for (Role r : roles) {
if (r.getDataType().equals(CommonConstant.DATA_TYPE_UNDER)) {
// 本部门及以下
if (StrUtil.isBlank(u.getDepartmentId())) {
// 用户无部门
deparmentIds.add("-1");
} else {
// 递归获取自己与子级
List<String> ids = new ArrayList<>();
getRecursion(u.getDepartmentId(), ids);
deparmentIds.addAll(ids);
}
} else if (r.getDataType().equals(CommonConstant.DATA_TYPE_SAME)) {
// 本部门
if (StrUtil.isBlank(u.getDepartmentId())) {
// 用户无部门
deparmentIds.add("-1");
} else {
deparmentIds.add(u.getDepartmentId());
}
} else if (r.getDataType().equals(CommonConstant.DATA_TYPE_CUSTOM)) {
// 自定义
List<String> depIds = iUserRoleService.findDepIdsByUserId(u.getId());
if (depIds == null || depIds.size() == 0) {
deparmentIds.add("-1");
} else {
deparmentIds.addAll(depIds);
}
}
}
// 去重
LinkedHashSet<String> set = new LinkedHashSet<>(deparmentIds.size());
set.addAll(deparmentIds);
deparmentIds.clear();
deparmentIds.addAll(set);
// 缓存
redisTemplate.set(key, new Gson().toJson(deparmentIds), 15L, TimeUnit.DAYS);
return deparmentIds;
}
private void getRecursion(String departmentId, List<String> ids) {
Department department = departmentService.get(departmentId);
ids.add(department.getId());
if (department.getIsParent() != null && department.getIsParent()) {
// 获取其下级
List<Department> departments = departmentService.findByParentIdAndStatusOrderBySortOrder(departmentId, CommonConstant.STATUS_NORMAL);
departments.forEach(d -> {
getRecursion(d.getId(), ids);
});
}
}
/**
* 通过用户名获取用户拥有权限
* @param username
*/
public List<GrantedAuthority> getCurrUserPerms(String username) {
List<GrantedAuthority> authorities = new ArrayList<>();
User user = userService.findByUsername(username);
if (user == null || user.getPermissions() == null || user.getPermissions().isEmpty()) {
return authorities;
}
for (PermissionDTO p : user.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(p.getTitle()));
}
return authorities;
}
}
5、代码实现
SpringSecurityConfig
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheManager);
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(8);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
}
UserDetailsServiceImpl
@Slf4j
@RequiredArgsConstructor
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final RoleService roleService;
private final DataService dataService;
private final UserCacheManager userCacheManager;
@Override
public JwtUserDto loadUserByUsername(String username) {
JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
if(jwtUserDto == null){
UserLoginDto user;
try {
user = userService.getLoginData(username);
} catch (EntityNotFoundException e) {
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
throw new UsernameNotFoundException(username, e);
}
if (user == null) {
throw new UsernameNotFoundException("");
} else {
if (!user.getEnabled()) {
throw new BadRequestException("账号未激活!");
}
jwtUserDto = new JwtUserDto(
user,
dataService.getDeptIds(user),
roleService.mapToGrantedAuthorities(user)
);
// 添加缓存数据
userCacheManager.addUserCache(username, jwtUserDto);
}
}
return jwtUserDto;
}
}
@ApiOperation("登录授权")
@AnonymousPostMapping(value = "/login")
public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
// 密码解密
String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
// 查询验证码
String code = (String) redisUtils.get(authUser.getUuid());
// 清除验证码
redisUtils.del(authUser.getUuid());
if (StringUtils.isBlank(code)) {
throw new BadRequestException("验证码不存在或已过期");
}
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
throw new BadRequestException("验证码错误");
}
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌与第三方系统获取令牌方式
// UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getUsername());
// Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication);
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
// 保存在线信息
onlineUserService.save(jwtUserDto, token, request);
// 返回 token 与 用户信息
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUserDto);
}};
if (loginProperties.isSingleLogin()) {
//踢掉之前已经登录的token
onlineUserService.checkLoginOnUser(authUser.getUsername(), token);
}
return ResponseEntity.ok(authInfo);
}
/**
* 保存在线用户信息
* @param jwtUserDto /
* @param token /
* @param request /
*/
public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){
String dept = jwtUserDto.getUser().getDept().getName();
String ip = StringUtils.getIp(request);
String browser = StringUtils.getBrowser(request);
String address = StringUtils.getCityInfo(ip);
OnlineUserDto onlineUserDto = null;
try {
onlineUserDto = new OnlineUserDto(jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
} catch (Exception e) {
log.error(e.getMessage(),e);
}
redisUtils.set(properties.getOnlineKey() + token, onlineUserDto, properties.getTokenValidityInSeconds()/1000);
}
/**
* 检测用户是否在之前已经登录,已经登录踢下线
* @param userName 用户名
*/
public void checkLoginOnUser(String userName, String igoreToken){
List<OnlineUserDto> onlineUserDtos = getAll(userName);
if(onlineUserDtos ==null || onlineUserDtos.isEmpty()){
return;
}
for(OnlineUserDto onlineUserDto : onlineUserDtos){
if(onlineUserDto.getUserName().equals(userName)){
try {
String token =EncryptUtils.desDecrypt(onlineUserDto.getKey());
if(StringUtils.isNotBlank(igoreToken)&&!igoreToken.equals(token)){
this.kickOut(token);
}else if(StringUtils.isBlank(igoreToken)){
this.kickOut(token);
}
} catch (Exception e) {
log.error("checkUser is error",e);
}
}
}
}
/**
* 根据用户名强退用户
* @param username /
*/
@Async
public void kickOutForUsername(String username) throws Exception {
List<OnlineUserDto> onlineUsers = getAll(username);
for (OnlineUserDto onlineUser : onlineUsers) {
if (onlineUser.getUserName().equals(username)) {
String token =EncryptUtils.desDecrypt(onlineUser.getKey());
kickOut(token);
}
}
}
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
/**
* @param tokenProvider Token
* @param properties JWT
* @param onlineUserService 用户在线
* @param userCacheManager 用户缓存工具
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheManager userCacheManager) {
this.properties = properties;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.userCacheManager = userCacheManager;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
boolean cleanUserCache = false;
try {
onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
userCacheManager.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
}
}
if (onlineUserDto != null && StringUtils.hasText(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 初步检测Token
*
* @param request /
* @return /
*/
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
}