Spring Security配置(包含WebSecurityConfigurerAdapter和authenticationManager()过时问题)
Spring Security配置(包含WebSecurityConfigurerAdapter和authenticationManager()过时问题)
·
Spring Security配置(包含WebSecurityConfigurerAdapter和authenticationManager()过时问题)
- pom.xml文件引入以下依赖
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.0</version>
</dependency>
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- 排除lettuce包,使用jedis代替-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
<version>2.7.0</version>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
- application.yml文件配置
# Tomcat
server:
port: 8088
tomcat:
uri-encoding: UTF-8
connection-timeout: 5000ms
threads:
max: 1000
min-spare: 30
servlet:
context-path: /gremlin
encoding:
charset: UTF-8
# spring配置
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/forum?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
redis:
database: 0 # Redis数据库索引(默认为0)
host: 127.0.0.1 # Redis服务器地址
port: 6379 # Redis服务器连接端口
jedis:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-idle: 8 # 连接池中的最大空闲连接 默认 8
min-idle: 0 # 连接池中的最小空闲连接 默认 0
- security配置 (编写SecurityConfig配置文件(WebSecurityConfigurerAdapter过时代替配置在文末))
package com.gremlin.config;
import com.gremlin.common.utils.RedisUtils;
import com.gremlin.log.service.LogService;
import com.gremlin.power.mapper.PowerManagerMapper;
import com.gremlin.power.service.PowerManagerService;
import com.gremlin.security.fifter.JwtAuthenticationTokenFilter;
import com.gremlin.security.fifter.TokenLoginFilter;
import com.gremlin.security.handler.AjaxAccessDeniedHandler;
import com.gremlin.security.handler.AjaxAuthenticationEntryPoint;
import com.gremlin.security.handler.AjaxLogoutSuccessHandler;
import com.gremlin.security.service.MyUserDetailsServiceImpl;
import com.gremlin.security.strategy.CustomizeSessionInformationExpiredStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Description: security配置类
* @author: gremlin
* Date: 2022/6/10 10:55
* @version: 1.0.0
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 注销成功返回的 JSON 格式数据给前端
*/
@Autowired
private AjaxLogoutSuccessHandler logoutSuccessHandler;
/**
* 无权访问 JSON 格式的数据
*/
@Autowired
private AjaxAccessDeniedHandler ajaxAccessDeniedHandler;
@Autowired
private AjaxAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
@Autowired
private JwtAuthenticationTokenFilter tokenAuthenticationFilter;
@Autowired
private RedisUtils redisUtils;
@Autowired
private LogService logService;
@Autowired
private PowerManagerMapper powerManagerMapper;
@Autowired
private MyUserDetailsServiceImpl userDetailsService;
@Autowired
private PowerManagerService powerManagerService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
http.cors().and().csrf().disable();
http.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
//自定义放行接口
.antMatchers(
"/swagger**/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/webjars/**",
"/v3/**"
).permitAll()
.anyRequest()
.authenticated()
.and().logout().logoutUrl("/logout")
//登出处理
.logoutSuccessHandler(logoutSuccessHandler)
//添加关于自定义的认证过滤器和自定义的授权过滤器
.and()
.logout().permitAll()//注销行为任意访问
//会话管理
.and().sessionManagement()
//同一账号同时登录最大用户数
.maximumSessions(1)
//会话信息过期策略会话信息过期策略(账号被挤下线)
.expiredSessionStrategy(sessionInformationExpiredStrategy);
//自定义权限拒绝处理类
// 无权访问 JSON 格式的数据
http.exceptionHandling().accessDeniedHandler(ajaxAccessDeniedHandler);
// 登录验证
http.addFilter(new TokenLoginFilter(authenticationManager(),redisUtils,logService,powerManagerMapper)).httpBasic();
// JWT Filter
http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- 编写UserDetailsService的实现类
package com.gremlin.security.service;
import com.gremlin.security.mapper.UserMapper;
import com.gremlin.security.vo.MyUserDetails;
import com.gremlin.security.vo.User;
import com.gremlin.security.vo.req.ReqUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* Description: 自定义的Service,实现于springSecurity框架的UserDetailsService
* @author: gremlin
* Date: 2022/6/10 13:02
* @version: 1.0.0
*/
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String loginUsername) throws UsernameNotFoundException {
ReqUser reqUser = new ReqUser();
reqUser.setLoginUsername(loginUsername);
// 通过账户查询账户信息
User userInfo = userMapper.getUserInfo(reqUser);
if (null == userInfo){
throw new UsernameNotFoundException("该用户不存在或被禁用请联系管理员");
}
String password = userInfo.getPassword();
//找到该账号下面的权限
return new MyUserDetails(loginUsername,password);
}
}
- 编写token登录过滤器
package com.gremlin.security.fifter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.gremlin.account.vo.req.ReqAccount;
import com.gremlin.common.resp.ResponseResult;
import com.gremlin.common.resp.ResultEnum;
import com.gremlin.common.utils.AccessAddressUtils;
import com.gremlin.common.utils.JwtTokenUtils;
import com.gremlin.common.utils.RedisUtils;
import com.gremlin.log.service.LogService;
import com.gremlin.power.mapper.PowerManagerMapper;
import com.gremlin.power.vo.resp.RespRole;
import com.gremlin.security.vo.MyUserDetails;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* Description: token登录过滤器
* @author: gremlin
* Date: 2022/6/10 10:55
* @version: 1.0.0
*/
@Slf4j
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private final RedisUtils redisUtil;
private final AuthenticationManager authenticationManager;
private final LogService logService;
private final PowerManagerMapper powerManagerMapper;
public TokenLoginFilter(AuthenticationManager authenticationManager, RedisUtils redisUtil,
LogService logService,PowerManagerMapper powerManagerMapper) {
this.authenticationManager = authenticationManager;
this.redisUtil = redisUtil;
this.logService = logService;
this.powerManagerMapper = powerManagerMapper;
}
/**
* 通过前端传来的json数据(用户名 密码),解析成我们的java对象,最终得到用户名和密码去进行认证
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
System.out.println(2);
//只允许post请求
if ("POST".equals(req.getMethod())) {
// 获取请求体
String body = getBody(req);
// 转换为json格式
JSONObject jsonObject = JSON.parseObject(body);
String username = jsonObject.getString("loginUsername");
String password = jsonObject.getString("password");
// 获取ip地址
String ip = AccessAddressUtils.getIpAddress(req);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
//插入用户登录成功日志
logService.insertLog(ip, "1", "登入", username);
return authenticationManager.authenticate(authRequest);
}
return null;
}
/**
* 若认证成功,根据用户名生成token返回给前端。并以用户名为key,权限列表为value的形式存入redis缓存中
*/
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException {
System.out.println(4);
Map<String, Object> map = new HashMap<>();
// 获取IP地址
String ip = AccessAddressUtils.getIpAddress(req);
map.put("ip", ip);
// 获取当前用户信息
MyUserDetails userDetails = (MyUserDetails) auth.getPrincipal();
// 通过jwt生成jwtToken
String jwtToken = JwtTokenUtils.generateToken(userDetails.getUsername(),map);
// 将token赋值到用户对象类
userDetails.setToken(jwtToken);
// 通过用户名称查询用户的角色
ReqAccount reqAccount = new ReqAccount();
reqAccount.setLoginUsername(userDetails.getUsername());
RespRole respRole = new RespRole();
respRole = powerManagerMapper.queryRoleByUsername(reqAccount);
map.put("juese",respRole);
//获取请求的ip地址
redisUtil.setTokenRefresh(userDetails.getUsername(), userDetails.getToken(), ip);
log.info("用户{}登录成功,信息已保存至redis", userDetails.getUsername());
res.setHeader("Content-type", "application/json;charset=UTF-8");
map.put("token", jwtToken);
redisUtil.set(userDetails.getUsername()+"token",jwtToken);
res.getWriter().write(JSON.toJSONString(ResponseResult.success(map, ResultEnum.SUCCESS.getCode(), ResultEnum.USER_LOGIN_SUCCESS.getMessage())));
}
/**
* 登录失败 若认证失败,则返回错误信息给前端
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException {
response.setHeader("Content-type", "application/json;charset=UTF-8");
if (e instanceof UsernameNotFoundException) {
response.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.USER_NOT_FIND.getMessage(), ResultEnum.USER_NOT_FIND.getCode())));
} else if (e instanceof BadCredentialsException) {
response.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.USER_LOGIN_FAILED.getMessage(), ResultEnum.USER_LOGIN_FAILED.getCode())));
} else if (e instanceof LockedException) {
response.getWriter().write(JSON.toJSONString(ResponseResult.failed("用户已被锁定", 207)));
}else {
response.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.USER_LOGIN_FAILED.getMessage(), ResultEnum.USER_LOGIN_FAILED.getCode())));
}
}
/**
* 获取请求Body
*/
public String getBody(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
- 编写token认证过滤器
package com.gremlin.security.fifter;
import com.alibaba.fastjson2.JSON;
import com.gremlin.common.resp.ResponseResult;
import com.gremlin.common.resp.ResultEnum;
import com.gremlin.common.utils.CollectionUtil;
import com.gremlin.common.utils.DateUtil;
import com.gremlin.common.utils.JwtTokenUtils;
import com.gremlin.common.utils.RedisUtils;
import com.gremlin.security.service.MyUserDetailsServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Description: token认证过滤器
* @author: gremlin
* Date: 2022/6/10 10:55
* @version: 1.0.0
*/
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private MyUserDetailsServiceImpl userDetailsService;
@Autowired
private RedisUtils redisUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println(1);
String authToken = request.getHeader("token");
response.setHeader("Content-type", "application/json;charset=UTF-8");
if (null != authToken) {
//判断token是否有效
if (!JwtTokenUtils.isTokenExpired(authToken)) {
response.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.LOGIN_IS_OVERDUE.getMessage(), ResultEnum.LOGIN_IS_OVERDUE.getCode())));
}
// 通过token获取用户名
String username = JwtTokenUtils.parseToken(authToken);
if (StringUtils.isBlank(username)) {
return;
}
String ip = CollectionUtil.getMapValue(JwtTokenUtils.getClaims(authToken), "ip");
//判断redis是否有保存
if (redisUtil.hasKey(username)) {
//有效时间
String expirationTime = redisUtil.hasGet(username, "expirationTime").toString();
if (JwtTokenUtils.isExpiration(expirationTime)) {
// token失效
//当前时间超过有效时间,用户登录失效
//获得redis中用户的token刷新时效
String tokenValidTime = (String) redisUtil.getTokenValidTimeByToken(username);
String currentTime = DateUtil.getTime();
//这个token已作废,加入黑名单
if (DateUtil.compareDate(currentTime, tokenValidTime) && !DateUtil.compareDate(tokenValidTime, expirationTime)) {
//超过有效期,不予刷新
log.info("{}已超过有效期,不予刷新", authToken);
response.getWriter().write(JSON.toJSONString(ResponseResult.success(null, ResultEnum.LOGIN_IS_OVERDUE.getCode(), ResultEnum.LOGIN_IS_OVERDUE.getMessage())));
return;
} else {
//仍在有效时间内,判断是否到达刷新时间,如果到达刷新时间
// (刷新时间判断方法为token时间-当前时间<=5min),刷新token否则不更新token
Date tokenTime = JwtTokenUtils.getTokenTime(authToken);
// 则刷新token,放入请求头中
String usernameByToken = (String) redisUtil.getUsernameByToken(username);
//更新username
username = usernameByToken;
//更新ip
ip = (String) redisUtil.getIpByToken(username);
//token中的时间与当前时间做对比,
if (DateUtil.compareDate(tokenTime, new Date())) {
Map<String, Object> map = new HashMap<>();
map.put("ip", ip);
String jwtToken = JwtTokenUtils.generateToken(usernameByToken,map);
redisUtil.setTokenRefresh(username, jwtToken, ip);
log.info("redis已删除旧token:{},新token:{}已更新redis", authToken, jwtToken);
//更新token,为了后面
authToken = jwtToken;
}
redisUtil.set(username + "token", authToken, 1200);
}
}
} else {
log.info("{}redis登录信息不存在", username);
response.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.LOGIN_IS_OVERDUE.getMessage(), ResultEnum.LOGIN_IS_OVERDUE.getCode())));
return;
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
response.reset();
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
filterChain.doFilter(request, response);
}
}
- 编写实现UserDetails的实体类
package com.gremlin.security.vo;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* Description: 自定义的实体类,实现于spring security框架的UserDetails
* @author: gremlin
* Date: 2022/6/10 13:06
* @version: 1.0.0
*/
public class MyUserDetails implements UserDetails {
@ApiModelProperty(value = "用户账户")
private String username;
@ApiModelProperty(value = "用户密码")
private String password;
@ApiModelProperty(value = "用户token值")
private String token;
public MyUserDetails(String username, String password) {
this.username = username;
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setToken(String token) {
this.token = token;
}
public String getToken() {
return token;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
- 编写无权访问响应类
package com.gremlin.security.handler;
import com.alibaba.fastjson2.JSON;
import com.gremlin.common.resp.ResponseResult;
import com.gremlin.common.resp.ResultEnum;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Description: 无权访问
* @author: gremlin
* Date: 2022/6/10 10:55
* @version: 1.0.0
*/
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setHeader("Content-type", "application/json;charset=UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.USER_NO_ACCESS.getMessage(),ResultEnum.USER_NO_ACCESS.getCode())));
}
}
- 编写token认证身份验证入口点
package com.gremlin.security.handler;
import com.alibaba.fastjson.JSON;
import com.gremlin.common.resp.ResponseResult;
import com.gremlin.common.resp.ResultEnum;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Description: 用户未登录时返回给前端的数据 身份验证入口点
* @author: gremlin
* Date: 2022/6/10 17:24
* @version: 1.0.0
*/
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.USER_NEED_AUTHORITIES.getMessage(),ResultEnum.USER_NEED_AUTHORITIES.getCode())));
}
}
- 编写自定义处理注销成功的类
package com.gremlin.security.handler;
import com.alibaba.fastjson2.JSON;
import com.gremlin.common.resp.ResponseResult;
import com.gremlin.common.resp.ResultEnum;
import com.gremlin.common.utils.JwtTokenUtils;
import com.gremlin.common.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Description: 自定义处理注销成功的类
* @author: gremlin
* Date: 2022/6/10 10:55
* @version: 1.0.0
*/
@Component
@Slf4j
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private RedisUtils redisUtil;
@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException {
// 获取头部信息的token
String authToken = req.getHeader("token");
if (null != authToken) {
String username = JwtTokenUtils.parseToken(authToken);
// 清除redis里的token等其他值
redisUtil.deleteKeys(username);
log.info("用户登出成功!token:{}已从redis删除", authToken);
}
resp.setHeader("Content-type", "application/json;charset=UTF-8");
resp.getWriter().write(JSON.toJSONString(ResponseResult.failed(ResultEnum.USER_LOGOUT_SUCCESS.getMessage(), ResultEnum.USER_LOGOUT_SUCCESS.getCode())));
}
}
- 编写会话信息过期策略类
package com.gremlin.security.strategy;
import com.alibaba.fastjson2.JSON;
import com.gremlin.common.resp.ResponseResult;
import com.gremlin.common.resp.ResultEnum;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Description: 会话信息过期策略
* @author: gremlin
* Date: 2022/6/10 10:55
* @version: 1.0.0
*/
@Component
public class CustomizeSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException {
HttpServletResponse httpServletResponse = sessionInformationExpiredEvent.getResponse();
httpServletResponse.setHeader("Content-type", "application/json;charset=UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(
ResponseResult.failed(ResultEnum.USER_NO_ACCESS.getMessage(),ResultEnum.USER_NO_ACCESS.getCode())));
}
}
- 编写结果响应json返回类
package com.gremlin.common.resp;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* Description: 结果响应json返回类
* @author: gremlin
* Date: 2022/6/9 15:54
* @version: 1.0.0
*/
@Slf4j
@Data
public class ResponseResult<T> implements Serializable {
@ApiModelProperty(value = "返回数据")
private T data;
@ApiModelProperty(value = "返回信息")
private String msg;
@ApiModelProperty(value = "结果code")
private Integer code;
@ApiModelProperty(value = "是否成功")
private Boolean isSuccess = true;
public ResponseResult() {
}
/**
* 成功
*/
public ResponseResult(T data,Integer code, String msg) {
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 成功
*/
public ResponseResult(T data,Integer code, String msg,Boolean isSuccess) {
this.code = code;
this.msg = msg;
this.data = data;
this.isSuccess = isSuccess;
}
/**
* 失败
*/
public ResponseResult(String msg,Integer code) {
this.code = code;
this.msg = msg;
}
/**
* 失败
*/
public ResponseResult(String msg) {
this.msg = msg;
}
/**
* 方便静态调用(成功)
*/
public static <T> ResponseResult<T> success(T data, Integer code, String msg) {
return new ResponseResult<T>(data,code,msg);
}
/**
* 方便静态调用(成功)
*/
public static <T> ResponseResult<T> success(T data, Integer code, String msg,Boolean isSuccess) {
return new ResponseResult<T>(data,code,msg,isSuccess);
}
/**
* 方便静态调用(失败)
*/
public static <T> ResponseResult<T> failed(String msg,Integer code) {
return new ResponseResult<T>(msg,code);
}
/**
* 方便静态调用(失败)
*/
public static <T> ResponseResult<T> failed(String msg) {
return new ResponseResult<T>(msg);
}
}
- 编写结果code枚举类
package com.gremlin.common.resp;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
/**
* Description: 结果code枚举类
* @author: gremlin
* Date: 2022/6/9 16:13
* @version: 1.0.0
*/
@Getter
public enum ResultEnum {
/**
* 请求成功
*/
SUCCESS(200,"请求成功"),
/**
* 请求失败
*/
FAILURE(102,"请求失败"),
/**
* 用户未登录
*/
USER_NEED_AUTHORITIES(201,"用户未登录"),
/**
* 用户登录成功
*/
USER_LOGIN_SUCCESS(203,"login success!"),
/**
* 用户账号或密码错误
*/
USER_LOGIN_FAILED(202,"用户账号或密码错误"),
/**
* 用户不存在
*/
USER_NOT_FIND(206,"该用户不存在或被禁用请联系管理员"),
/**
* 用户登出成功
*/
USER_LOGOUT_SUCCESS(205,"登出成功!"),
/**
* 用户无权访问
*/
USER_NO_ACCESS(301,"用户无权访问"),
/**
* 您的操作已超时,请重新登录
*/
LOGIN_IS_OVERDUE(204,"您的操作已超时,请重新登录"),
/**
* 手机号码不正确
*/
ERROR_PHONE(50001,"手机号码不正确"),
/**
* 邮箱地址不正确
*/
ERROR_EMAIL(50002,"邮箱地址不正确"),
/**
* 验证码错误
*/
ERROR_CODE(50003,"验证码错误"),
/**
* 邮件发送成功
*/
SEND_EMAIL(205,"邮件发送成功!")
;
@ApiModelProperty(value = "返回code")
private final Integer code;
@ApiModelProperty(value = "返回信息")
private final String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
- 编写工具类ip地址获取工具类
package com.gremlin.common.utils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* Description: ip地址获取工具类
* @author: gremlin
* Date: 2022/6/10 15:11
* @version: 1.0.0
*/
@Component
public class AccessAddressUtils {
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 参考文章: http://developer.51cto.com/art/201111/305181.htm
*
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
* 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
- 编写工具类日期处理工具类
package com.gremlin.common.utils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Description: 日期处理工具类
* @author: gremlin
* Date: 2022/6/10 15:17
* @version: 1.0.0
*/
public class DateUtil {
private final static SimpleDateFormat SDF_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final SimpleDateFormat SDF_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
/**
* 获取当前时间的YYYY-MM-DD HH:mm:ss格式
*/
public static String getTime() {
return SDF_TIME.format(new Date());
}
/**
* 日期比较,如果s>=e 返回true 否则返回false
*/
public static boolean compareDate(String s, String e) {
if(fomatDate(s)==null||fomatDate(e)==null){
return false;
}
return s.compareTo(e)>0;
}
/**
* 日期比较,如果s>=e 返回true 否则返回false
*/
public static boolean compareDate(Date s, Date e) {
if(s==null||e==null){
return false;
}
return s.getTime()-e.getTime()<=5*60*1000;
}
/**
* 格式化日期
*/
public static Date fomatDate(String date) {
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
try {
return fmt.parse(date);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取当前时间的后i天
*/
public static String getAddDay(int i){
String currentTime = DateUtil.getTime();
GregorianCalendar gCal = new GregorianCalendar(
Integer.parseInt(currentTime.substring(0, 4)),
Integer.parseInt(currentTime.substring(5, 7)) - 1,
Integer.parseInt(currentTime.substring(8, 10)));
gCal.add(GregorianCalendar.DATE, i);
return SDF_DATE_FORMAT.format(gCal.getTime());
}
/**
* 获取当前时间的后i天 精确到秒
*/
public static String getAddDayTime(int i){
Date date = new Date(System.currentTimeMillis()+ (long) i *24*60*60*1000);
return SDF_TIME.format(date);
}
/**
* 获取当前时间的+多少秒 精确到秒
*/
public static String getAddDaySecond(int i){
Date date = new Date(System.currentTimeMillis()+i* 1000L);
return SDF_TIME.format(date);
}
}
- 编写Jwt生成token工具类
package com.gremlin.common.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;
/**
* Description: Jwt生成token工具类
* @author: gremlin
* Date: 2022/6/10 15:17
* @version: 1.0.0
*/
@Slf4j
public class JwtTokenUtils {
@ApiModelProperty(value = "令牌签名密钥")
private static final String tokenSignKey = "gremlin";
@ApiModelProperty(value = "token有效时长30分钟")
private static final long tokenExpiration = 30*60*1000;
/**
* 生成token
*/
public static String generateToken(String subject, Map<String,Object> claims) {
return Jwts.builder().setClaims(claims)
.setSubject(subject)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
// 签名算法
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
// 压缩格式
.compressWith(CompressionCodecs.GZIP).compact();
}
/**
* 判断令牌是否过期
*/
public static Boolean isTokenExpired(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
/**
* 解析token,获得subject中的信息
*/
public static String parseToken(String token) {
String subject = null;
try {
subject = getTokenBody(token).getSubject();
} catch (Exception e) {
log.info(String.valueOf(e));
}
return subject;
}
/**
* 获取token自定义属性
*/
public static Map<String,Object> getClaims(String token){
Map<String,Object> claims = null;
try {
claims = getTokenBody(token);
}catch (Exception e) {
e.printStackTrace();
}
return claims;
}
public static Date getTokenTime(String token){
return getTokenBody(token).getExpiration();
}
/**
* 是否已过期
*/
public static boolean isExpiration(String expirationTime){
//通过redis中的失效时间进行判断
String currentTime = DateUtil.getTime();
if(DateUtil.compareDate(currentTime,expirationTime)){
//当前时间比过期时间大,失效
return true;
}else{
return false;
}
}
private static Claims getTokenBody(String token){
return Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
}
/**
* 根据token字符串获取账号
* @param request
* @return
*/
public static String getUserByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(!StringUtils.hasText(jwtToken)) {
return "";
}
Claims tokenBody = getTokenBody(jwtToken);
return tokenBody.getSubject();
}
}
- 编写redis工具配置类
package com.gremlin.common.utils;
import com.gremlin.security.vo.User;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Description: redis工具类
* @author: gremlin
* Date: 2022/6/10 14:43
* @version: 1.0.0
*/
@Component
public class RedisUtils {
@Autowired
private StringRedisTemplate redisTemplate;
@ApiModelProperty(value = "reids 中用户信息吗存储2小时 刷新时间")
private final int validTime = 2;
@ApiModelProperty(value = "过期时间 秒")
private final int expirationSeconds = 300;
/**
* 删除精确key值下所有值
*/
public void deleteKey(String key) {
redisTemplate.opsForHash().getOperations().delete(key);
}
/**
* 删除key前缀值下所有值
*/
public void deleteKeys(String key) {
//最后一定要带上 *
Set<String> keys = redisTemplate.keys(key + "*");
if (CollectionUtils.isNotEmpty(keys)){
redisTemplate.delete(keys);
}
}
/**
* 删出key 这里跟下边deleteKey()最底层实现都是一样的,应该可以通用
*/
public void delete(String key){
redisTemplate.opsForValue().getOperations().delete(key);
}
/**
* 字符串获取值
*/
public Object get(String key){
return redisTemplate.opsForValue().get(key);
}
/**
* 字符串存入值 默认过期时间为2小时
*/
public void set(String key, String value){
redisTemplate.opsForValue().set(key,value, 7200, TimeUnit.SECONDS);
}
/**
* 字符串存入值
*/
public void set(String key, String value,Integer expire){
redisTemplate.opsForValue().set(key,value, expire,TimeUnit.SECONDS);
}
/**
* 查询token下的刷新时间
*/
public Object getTokenValidTimeByToken(String token) {
return redisTemplate.opsForHash().get(token, "tokenValidTime");
}
/**
* 查询token下的刷新时间
*
* @param token 查询的key
* @return HV
*/
public Object getUsernameByToken(String token) {
return redisTemplate.opsForHash().get(token, "username");
}
/**
* 查询token下的刷新时间
*
* @param token 查询的key
* @return HV
*/
public Object getIpByToken(String token) {
return redisTemplate.opsForHash().get(token, "ip");
}
public void setTokenRefresh(String username,String token,String ip){
//刷新时间
Integer expire = validTime*60*60*1000;
hset(username, "tokenValidTime",DateUtil.getAddDayTime(validTime),expire);
hset(username, "expirationTime",DateUtil.getAddDaySecond(expirationSeconds),expire);
hset(username, "username",username,expire);
hset(username, "token",token,expire);
hset(username, "ip",ip,expire);
}
/**
* 添加单个
*/
public void hset(String key,String filed,Object domain,Integer expire){
redisTemplate.opsForHash().put(key, filed, domain);
redisTemplate.expire(key, expire,TimeUnit.SECONDS);
}
/**
* 判断key和field下是否有值
*/
public Boolean hasKey(String key,String field) {
return redisTemplate.opsForHash().hasKey(key,field);
}
/**
* 判断key下是否有值
*/
public Boolean hasKey(String key) {
return redisTemplate.opsForHash().getOperations().hasKey(key);
}
/**
* 查询key和field所确定的值
*/
public Object hasGet(String key,String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 查询该key下所有值
*/
public Object hasGet(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 获取用户登录信息
*/
public static User getUser(){
// 获取当前用户
UserDetails userDetails
= (UserDetails) SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
User user = new User();
user.setUsername(userDetails.getUsername());
return user;
}
}
- 编写token响应类 获取token
package com.gremlin.common.utils;
import com.gremlin.security.vo.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
/**
* Description: token响应类 获取token
* @author: gremlin
* Date: 2022/6/10 10:55
* @version: 1.0.0
*/
@Service
public class TokenResponse {
@Autowired
private RedisUtils redisUtil;
public void getResponse(HttpServletResponse response) {
//获取用户登录信息
User user = RedisUtils.getUser();
// 获取Token
Object token = redisUtil.get(user.getUsername()+"token");
if (StringUtils.isNotBlank((String)token)) {
response.reset();
response.setHeader("token", String.valueOf(token));
} else {
response.reset();
response.setHeader("token", String.valueOf(token));
}
}
}
- mapper类以及数据库查询类很简单就不写了
追加:WebSecurityConfigurerAdapter和authenticationManager()过时配置SecurityConfig
package com.gremlin.config;
import com.gremlin.common.utils.RedisUtils;
import com.gremlin.log.service.LogService;
import com.gremlin.power.mapper.PowerManagerMapper;
import com.gremlin.power.service.PowerManagerService;
import com.gremlin.security.fifter.JwtAuthenticationTokenFilter;
import com.gremlin.security.fifter.TokenLoginFilter;
import com.gremlin.security.handler.AjaxAccessDeniedHandler;
import com.gremlin.security.handler.AjaxAuthenticationEntryPoint;
import com.gremlin.security.handler.AjaxLogoutSuccessHandler;
import com.gremlin.security.strategy.CustomizeSessionInformationExpiredStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Description:
*
* @author: gremlin
* Date: 2022/7/12 17:10
* @version: 1.0.0
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
/**
* 注销成功返回的 JSON 格式数据给前端
*/
@Autowired
private AjaxLogoutSuccessHandler logoutSuccessHandler;
/**
* 无权访问 JSON 格式的数据
*/
@Autowired
private AjaxAccessDeniedHandler ajaxAccessDeniedHandler;
@Autowired
private AjaxAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
@Autowired
private JwtAuthenticationTokenFilter tokenAuthenticationFilter;
@Autowired
private RedisUtils redisUtils;
@Autowired
private LogService logService;
@Autowired
private PowerManagerMapper powerManagerMapper;
@Autowired
private PowerManagerService powerManagerService;
/**
* 注入AuthenticationConfiguration
*/
@Autowired
private AuthenticationConfiguration auth;
/**
* 编写AuthenticationManager的bean
*/
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return auth.getAuthenticationManager();
}
/**
* 替换旧版本中的configure(HttpSecurity http)方法
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
//自定义放行接口
.antMatchers(
"/swagger**/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/webjars/**",
"/v3/**"
).permitAll()
.anyRequest()
.authenticated()
.and().logout().logoutUrl("/logout")
//登出处理
.logoutSuccessHandler(logoutSuccessHandler)
//添加关于自定义的认证过滤器和自定义的授权过滤器
.and()
.logout().permitAll()//注销行为任意访问
//会话管理
.and().sessionManagement()
//同一账号同时登录最大用户数
.maximumSessions(1)
//会话信息过期策略会话信息过期策略(账号被挤下线)
.expiredSessionStrategy(sessionInformationExpiredStrategy);
//自定义权限拒绝处理类
// 无权访问 JSON 格式的数据
http.exceptionHandling().accessDeniedHandler(ajaxAccessDeniedHandler);
// 登录验证
http.addFilter(new TokenLoginFilter(authenticationManager(),redisUtils,logService,powerManagerMapper,powerManagerService)).httpBasic();
// JWT Filter
http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
更多推荐
已为社区贡献2条内容
所有评论(0)