- 导入依赖
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
javax.xml.bind
jaxb-api
2.3.0
com.sun.xml.bind
jaxb-impl
2.3.0
com.sun.xml.bind
jaxb-core
2.3.0
javax.activation
activation
1.1.1
- IpUtil工具类
package com.xxz.common.utils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 获取ip地址
*/
public class IpUtil {
public static String getIpAddress(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress="";
}
// ipAddress = this.getRequest().getRemoteAddr();
return ipAddress;
}
public static String getGatwayIpAddress(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress().getAddress().getHostAddress();
}
return ip;
}
}
- JwtHelper工具类
package com.xxz.common.utils;
import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;
import java.util.Date;
/**
* 生成JSON Web令牌的工具类
*/
public class JwtHelper {
//token过期时间
private static long tokenExpiration = 365 * 24 * 60 * 60 * 1000;
//加密秘钥
private static String tokenSignKey = "1234567890";
//根据用户id和用户名称生成token字符串
public static String createToken(String userId, String username) {
String token = Jwts.builder()
.setSubject("AUTH-USER") //设置主题
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) //设置过期时间
.claim("userId", userId) //有效载荷
.claim("username", username) //有效载荷
.signWith(SignatureAlgorithm.HS512, tokenSignKey) //HS512集成自定义密钥方式加密
.compressWith(CompressionCodecs.GZIP) //字符串压缩处理
.compact();
return token;
}
//从token字符串获取userid
public static String getUserId(String token) {
try {
if (StringUtils.isEmpty(token)) return null;
// Jwt解析(parse) , 通过密钥(tokenSignKey) 进行
Jws claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody(); //获取载荷主体部分
String userId = (String) claims.get("userId"); //获取有效信息
return userId;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//从token字符串获取username
public static String getUsername(String token) {
try {
if (StringUtils.isEmpty(token)) return "";
Jws claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String) claims.get("username");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//无需定义token删除,客户端扔掉即可
public static void main(String[] args) {
String token = JwtHelper.createToken("1", "test");
System.out.println(token);
String userId = JwtHelper.getUserId(token);
System.out.println(userId);
String username = JwtHelper.getUsername(token);
System.out.println(username);
}
}
- MD5工具类
package com.xxz.common.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class MD5 {
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
}
- ResponseUtil响应工具类
package com.xxz.common.utils;
import com.xxz.common.result.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ResponseUtil {
public static void out(HttpServletResponse response, Result r) {
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try {
mapper.writeValue(response.getWriter(), r);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 自定义密码组件 MD5
package com.xxz.system.custom;
import com.xxz.common.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
//一、自定义密码处理组件
@Component
public class CustomMD5Password implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
//进行MD5加密
return MD5.encrypt(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodePassword) {
//判断是否相等
return encodePassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
- 自定义用户对象
package com.xxz.system.custom;
import com.xxz.model.system.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
//二、自定义用户类(继承Security里的User类)
public class CustomUser extends User {
/**
* 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象
*/
private SysUser sysUser;
public CustomUser(SysUser sysUser, Collection extends GrantedAuthority> authorities){
//调用父类构造器初始化信息
super(sysUser.getUsername(), sysUser.getPassword(), authorities);
this.sysUser = sysUser;
}
public SysUser getSysUser() {
return sysUser;
}
public void setSysUser(SysUser sysUser) {
this.sysUser = sysUser;
}
@Override
public String toString() {
return "CustomUser{" +
"sysUser=" + sysUser +
'}';
}
}
- 自定义业务查询方法
package com.xxz.system.service;
import com.xxz.model.system.SysUser;
import com.xxz.system.custom.CustomUser;
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.Component;
import java.util.Collections;
//三、自定义根据前端数据的用户参数-获取用户数据的业务类及业务查询方法,默认时从内存中获取查询用户信息(需要在Security配置类中配置内存相关存储用户信息)
@Component
public class UserDetailServiceImpl implements UserDetailsService {
//定义自己的业务类,用于查询数据库信息
@Autowired
private SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用业务类查询数据库
SysUser sysUser = sysUserService.getUserInfoByUserName(username);
//判断用户是否为空
if(null == sysUser){
throw new UsernameNotFoundException("用户不存在");
}
//判断用户状态是否可用
if(sysUser.getStatus().intValue() == 0){
throw new RuntimeException("用户已禁用...");
}
//返回自定义角色对象信息
return new CustomUser(sysUser, Collections.emptyList());
}
}
- 自定义认证过滤器
package com.xxz.system.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxz.common.result.Result;
import com.xxz.common.result.ResultCodeEnum;
import com.xxz.common.utils.JwtHelper;
import com.xxz.common.utils.ResponseUtil;
import com.xxz.model.vo.LoginVo;
import com.xxz.system.custom.CustomUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
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.HashMap;
import java.util.Map;
/**
* 登入过滤器,继承UserNamePasswordAuthenticationFilter , 对用户名密码进行登录校验
*/
//四、自定义认证过滤器 //继承 UsernamePasswordAuthenticationFilter
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
//定义构造器用于做当前登录过滤器初始化
public TokenLoginFilter(AuthenticationManager authenticationManager){
//设置/初始化 认证管理器
this.setAuthenticationManager(authenticationManager);
//取消 只针对post请求
this.setPostOnly(false);
//指定登录接口及提交方式,可以指定任意路径
this.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/admin/system/index/login", "POST"));
}
//重写获取用户信息方法
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
//以流的方式获取前端请求接口封装的用户对象
LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);
//创建UsernamePasswordAuthenticationToken对象,封装用户名和密码,得到认证对象
Authentication authenticationToken =
new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
//返回目标认证对象
Authentication authentication = this.getAuthenticationManager().authenticate(authenticationToken);
System.out.println("authenticate ===== " + authentication);
return authentication;
}catch (IOException e){
e.printStackTrace();
}
return null;
}
//重写登录成功调用/执行
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
//auth : 表示当前认证对象
//1.获取认证对象
CustomUser customUser = (CustomUser) auth.getPrincipal();
System.out.println("===========================================================");
System.out.println("===========================================================");
System.out.println("===========================================================");
System.out.println(" auth.getDetails(); : ==== " + auth);
System.out.println("===========================================================");
System.out.println("===========================================================");
System.out.println("===========================================================");
//2.生成token
String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());
//3.返回(通过响应工具)
Map map = new HashMap<>();
map.put("token", token);
ResponseUtil.out(response, Result.ok(map));
}
//重写登录失败调用/执行
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
//判断当前异常是否属于运行时异常
if(e.getCause() instanceof RuntimeException){
ResponseUtil.out(response, Result.build(null, 204, e.getMessage()));
}else{
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR));
}
}
}
- 自定义同一返回结果 Result and ResultCodeEnum
package com.xxz.common.result;
import lombok.Data;
/**
* 统一放回结果 [五、自定义同一返回结果 Result and ResultCodeEnum]
* @param
*/
@Data
public class Result {
//返回码
private Integer code;
//返回消息
private String message;
//返回数据
private T data;
public Result(){}
// 返回数据
protected static Result build(T data) {
Result result = new Result();
if (data != null)
result.setData(data);
return result;
}
public static Result build(T body, Integer code, String message) {
Result result = build(body);
result.setCode(code);
result.setMessage(message);
return result;
}
public static Result build(T body, ResultCodeEnum resultCodeEnum) {
Result result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
public static Result ok(){
return Result.ok(null);
}
/**
* 操作成功
* @param data baseCategory1List
* @param
* @return
*/
public static Result ok(T data){
Result result = build(data);
return build(data, ResultCodeEnum.SUCCESS);
}
public static Result fail(){
return Result.fail(null);
}
/**
* 操作失败
* @param data
* @param
* @return
*/
public static Result fail(T data){
Result result = build(data);
return build(data, ResultCodeEnum.FAIL);
}
public Result message(String msg){
this.setMessage(msg);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
}
package com.xxz.common.result;
import lombok.Getter;
@Getter
public enum ResultCodeEnum {
SUCCESS(200,"成功"),
FAIL(201, "失败"),
SERVICE_ERROR(2012, "服务异常"),
DATA_ERROR(204, "数据异常"),
ILLEGAL_REQUEST(205, "非法请求"),
REPEAT_SUBMIT(206, "重复提交"),
ARGUMENT_VALID_ERROR(210, "参数校验异常"),
LOGIN_AUTH(208, "未登陆"),
PERMISSION(209, "没有权限"),
ACCOUNT_ERROR(214, "账号不正确"),
PASSWORD_ERROR(215, "密码不正确"),
LOGIN_MOBLE_ERROR( 216, "账号不正确"),
ACCOUNT_STOP( 217, "账号已停用"),
NODE_ERROR( 218, "该节点下有子节点,不可以删除")
;
private Integer code;
private String message;
private ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
- 自定义认证解析过滤器
package com.xxz.system.filter;
import com.xxz.common.result.Result;
import com.xxz.common.result.ResultCodeEnum;
import com.xxz.common.utils.JwtHelper;
import com.xxz.common.utils.ResponseUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
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.Collections;
//六、自定义认证解析过滤器
public class TokenAuthenticationFilter extends OncePerRequestFilter {
public TokenAuthenticationFilter(){
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
logger.info("uri: " + request.getRequestURI());
//如果时登录接口,直接放行(每个项目的登录接口不同)
if("/admin/system/index/login".equals(request.getRequestURI())){
//放行
filterChain.doFilter(request, response);
//停止运行
return;
}
//调用自定义方法,获取用户信息认证对象
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
//如果不为空,则存储到SecurityContextHolder对象中
if(null != authentication){
SecurityContextHolder.getContext().setAuthentication(authentication);
//放行
filterChain.doFilter(request, response);
}else{
//如果为空,则返回结果信息
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.PERMISSION));
}
}
//定义获取用户信息认证对象,通过token获取
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
//token置于header里面
String token = request.getHeader("token");
logger.info("token: " + token);
//token不为空则获取token中的用户信息
if(!StringUtils.isEmpty(token)){
String username = JwtHelper.getUsername(token);
logger.info("username: " + username);
//如果用户名不为空则返回目标认证对象
if(!StringUtils.isEmpty(username)){
return new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
}
}
return null;
}
}
- Security核心配置类
package com.xxz.system.config;
import com.xxz.system.custom.CustomMD5Password;
import com.xxz.system.filter.TokenAuthenticationFilter;
import com.xxz.system.filter.TokenLoginFilter;
import com.xxz.system.service.UserDetailServiceImpl;
import lombok.extern.slf4j.Slf4j;
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 六、配置SpirngSecurity配置类(自定义大整合)
*/
@Configuration
@EnableWebSecurity //开启SpringSecurity的默认行为
@Slf4j //日志
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
//导入自己编写的查询业务类(默认是providerService去内存查找)
@Autowired
private UserDetailServiceImpl userDetailsService;
//导入我们自己编写的加密工具类
@Autowired
private CustomMD5Password customMD5Password;
/**
* 用户认证管理器
* @return
* @throws Exception
*/
@Bean
@Override
protected AuthenticationManager authenticationManager()throws Exception{
log.info("AuthenticationManager用户认证管理器 + 加入容器.......");
log.info("当前注入对象UserDetailsService : ===== " + userDetailsService);
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http)throws Exception{
//这是配置的关键,决定那些接口开启防护,那些接口绕过防护
http
//关闭csrf
.csrf().disable()
//开启跨域以便前端调用接口
.cors()
.and()
.authorizeRequests()
//指定某些接口不需要通过认证即可访问, 登录接口肯定是不需要的
.antMatchers("/admin/system/index/login").permitAll()
//这里的意思是其他所有接口需要认证才能访问
.anyRequest().authenticated()
.and()
//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面
.addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilter(new TokenLoginFilter(authenticationManager()));
//禁用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//指定UserDetailService和加密器(使用自定义的)
log.info("指定UserDetailService和加密器(使用自定义的) + 加入容器.......");
auth.userDetailsService(userDetailsService).passwordEncoder(customMD5Password);
}
/**
* 配置那些请求不拦截
* 排除swagger 相关请求
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web)throws Exception{
web.ignoring().antMatchers(
"/favicon.ico",
"/swagger-resources/**",
"/webjars/**", "/v2/**",
"/swagger-ui.html",
"/doc.html");
}
}
- 修改loadUserByUsername查询用户权限操作数据返回
package com.xxz.system.service;
import com.xxz.model.system.SysMenu;
import com.xxz.model.system.SysUser;
import com.xxz.model.vo.RouterVo;
import com.xxz.system.custom.CustomUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//三、自定义根据前端数据的用户参数-获取用户数据的业务类及业务查询方法,默认时从内存中获取查询用户信息(需要在Security配置类中配置内存相关存储用户信息)
@Component
public class UserDetailServiceImpl implements UserDetailsService {
//定义自己的业务类,用于查询数据库信息
@Autowired
private SysUserService sysUserService;
//注入菜单模块业务
@Autowired
private SysMenuService sysMenuService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用业务类查询数据库
SysUser sysUser = sysUserService.getUserInfoByUserName(username);
//判断用户是否为空
if(null == sysUser){
throw new UsernameNotFoundException("用户不存在");
}
//判断用户状态是否可用
if(sysUser.getStatus().intValue() == 0){
throw new RuntimeException("用户已禁用...");
}
//返回自定义角色对象信息
//[授权 new :根据用户userid查询操作权限数据(获取当前用户所有->权限操作符)]
List userPermsList = sysMenuService.findUserButtonListByUserId(sysUser.getId());
//转换成Security要求的格式数据
List authorities = new ArrayList<>();
//遍历所有权限操作符
for (String perm : userPermsList) {
authorities.add(new SimpleGrantedAuthority(perm.trim()));
}
// [old return]
// return new CustomUser(sysUser, Collections.emptyList());
//[new return ]
return new CustomUser(sysUser, authorities);
}
}
- 配置Redis,存储当前用户权限数据SpringSecurity配置(将权限数据封装成SpringSecurity中的Authentication类)
org.springframework.boot
spring-boot-starter-data-redis
# server
server:
port: 8800
# mp
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #查看日志
mapper-locations: classpath:mapper/*.xml
# spring
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/guigu-auth?characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=convertToNull
username: root
password: root
# 日期格式问题
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# redis
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 1800000
password:
jedis:
pool:
max-active: 20 #最大连接数
max-wait: -1 #最大阻塞等待时间(负数表示没有限制)
max-idle: 5 #最大空闲
min-idle: 0 #最小空闲
- 修改过滤器
3.1认证过滤器
package com.xxz.system.filter;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxz.common.result.Result;
import com.xxz.common.result.ResultCodeEnum;
import com.xxz.common.utils.JwtHelper;
import com.xxz.common.utils.ResponseUtil;
import com.xxz.model.vo.LoginVo;
import com.xxz.system.custom.CustomUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
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.HashMap;
import java.util.Map;
/**
* 登入过滤器,继承UserNamePasswordAuthenticationFilter , 对用户名密码进行登录校验
*/
//四、自定义认证过滤器 //继承 UsernamePasswordAuthenticationFilter
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
//[new 授权]
private RedisTemplate redisTemplate;
//定义构造器用于做当前登录过滤器初始化 [new 授权]
public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate){
//设置/初始化 认证管理器
this.setAuthenticationManager(authenticationManager);
//取消 只针对post请求
this.setPostOnly(false);
//指定登录接口及提交方式,可以指定任意路径
this.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/admin/system/index/login", "POST"));
//[new 授权]
this.redisTemplate = redisTemplate;
}
//重写获取用户信息方法
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
//以流的方式获取前端请求接口封装的用户对象
LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);
//创建UsernamePasswordAuthenticationToken对象,封装用户名和密码,得到认证对象
Authentication authenticationToken =
new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
//返回目标认证对象
Authentication authentication = this.getAuthenticationManager().authenticate(authenticationToken);
System.out.println("authenticate ===== " + authentication);
return authentication;
}catch (IOException e){
e.printStackTrace();
}
return null;
}
//重写登录成功调用/执行
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
//auth : 表示当前认证对象
//1.获取认证对象
CustomUser customUser = (CustomUser) auth.getPrincipal();
System.out.println("===========================================================");
System.out.println("===========================================================");
System.out.println("===========================================================");
System.out.println(" auth.getDetails(); : ==== " + auth);
System.out.println("===========================================================");
System.out.println("===========================================================");
System.out.println("===========================================================");
//[new 授权]:认证成功后,将用户数据存储到redis中 (以用户名称作为key,以Json作为存储数据)
redisTemplate.opsForValue().set(customUser.getUsername(),
JSON.toJSONString(customUser.getAuthorities()));
//2.生成token
String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());
//3.返回(通过响应工具)
Map map = new HashMap<>();
map.put("token", token);
ResponseUtil.out(response, Result.ok(map));
}
//重写登录失败调用/执行
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
//判断当前异常是否属于运行时异常
if(e.getCause() instanceof RuntimeException){
ResponseUtil.out(response, Result.build(null, 204, e.getMessage()));
}else{
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR));
}
}
}
3.2解析过滤器
...
- 在项目中配置redis,完成controller相关代码
package com.xxz.system.filter;
import com.alibaba.fastjson.JSON;
import com.xxz.common.result.Result;
import com.xxz.common.result.ResultCodeEnum;
import com.xxz.common.utils.JwtHelper;
import com.xxz.common.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
//六、自定义认证解析过滤器
public class TokenAuthenticationFilter extends OncePerRequestFilter {
//[new 授权]:定义redis
private RedisTemplate redisTemplate;
//[new 授权]:引入redis/初始化redis对象
public TokenAuthenticationFilter(RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
logger.info("uri: " + request.getRequestURI());
//如果时登录接口,直接放行(每个项目的登录接口不同)
if("/admin/system/index/login".equals(request.getRequestURI())){
//放行
filterChain.doFilter(request, response);
//停止运行
return;
}
//调用自定义方法,获取用户信息认证对象
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
//如果不为空,则存储到SecurityContextHolder对象中
if(null != authentication){
SecurityContextHolder.getContext().setAuthentication(authentication);
//放行
filterChain.doFilter(request, response);
}else{
//如果为空,则返回结果信息
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.PERMISSION));
}
}
//定义获取用户以及权限信息对象,通过token获取(new 授权:从redis中获取用户信息,包括权限信息)
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
//token置于header里面
String token = request.getHeader("token");
logger.info("token: " + token);
//token不为空则获取token中的用户信息
if(!StringUtils.isEmpty(token)){
String username = JwtHelper.getUsername(token);
logger.info("username: " + username);
//如果用户名不为空则返回目标认证对象
if(!StringUtils.isEmpty(username)){
//[new return 授权 ] : 同时获取权限信息
//根据用户名 key ,从redis中获取当前用户目标权限数据(所有权限操作符)
String authoritiesString = (String)redisTemplate.opsForValue().get(username);
//将权限数据转换成map集合(方便操作)
List
- MySecurityConfig核心配置类也要修改
package com.xxz.system.config;
import com.xxz.system.custom.CustomMD5Password;
import com.xxz.system.filter.TokenAuthenticationFilter;
import com.xxz.system.filter.TokenLoginFilter;
import com.xxz.system.service.UserDetailServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 六、配置SpirngSecurity配置类(自定义大整合)
*/
@Configuration
@EnableWebSecurity //开启SpringSecurity的默认行为
@Slf4j //日志
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启注解功能,默认禁用注解
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
//导入自己编写的查询业务类(默认是providerService去内存查找)
@Autowired
private UserDetailServiceImpl userDetailsService;
//导入我们自己编写的加密工具类
@Autowired
private CustomMD5Password customMD5Password;
//[new 权限]:注入redistemplate依赖
@Autowired
private RedisTemplate redisTemplate;
/*
* 用户认证管理器
* @return
* @throws Exception
*/
@Bean
@Override
protected AuthenticationManager authenticationManager()throws Exception{
log.info("AuthenticationManager用户认证管理器 + 加入容器.......");
log.info("当前注入对象UserDetailsService : ===== " + userDetailsService);
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http)throws Exception{
//这是配置的关键,决定那些接口开启防护,那些接口绕过防护
http
//关闭csrf
.csrf().disable()
//开启跨域以便前端调用接口
.cors()
.and()
.authorizeRequests()
//指定某些接口不需要通过认证即可访问, 登录接口肯定是不需要的
.antMatchers("/admin/system/index/login").permitAll()
//这里的意思是其他所有接口需要认证才能访问
.anyRequest().authenticated()
.and()
//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面 [new 权限:注入redistemplate]
.addFilterBefore(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)
.addFilter(new TokenLoginFilter(authenticationManager(), redisTemplate));
//禁用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//指定UserDetailService和加密器(使用自定义的)
log.info("指定UserDetailService和加密器(使用自定义的) + 加入容器.......");
auth.userDetailsService(userDetailsService).passwordEncoder(customMD5Password);
}
/**
* 配置那些请求不拦截
* 排除swagger 相关请求
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web)throws Exception{
web.ignoring().antMatchers(
"/favicon.ico",
"/swagger-resources/**",
"/webjars/**", "/v2/**",
"/swagger-ui.html",
"/doc.html");
}
}
- 通过Controller进行Security权限测试——以角色列表分页查询
//逻辑删除
@ApiOperation("逻辑删除接口")
@PreAuthorize("hasAuthority('bnt.sysRole.remove')")
@DeleteMapping("/remove/{id}")
public Result removeRole(@PathVariable("id") Long id){
boolean bool = sysRoleService.removeById(id);
if(bool){
return Result.ok();
}else{
return Result.fail();
}
}
//查询所有
@ApiOperation("查询所有角色接口")
@PreAuthorize("hasAuthority('bnt.sysRole.list')")
@GetMapping("/findAll")
public Result findAllRole(){
return Result.ok(sysRoleService.list());
}
//分页查询
@ApiOperation("条件分页查询")
@PreAuthorize("hasAuthority('bnt.sysRole.list')")
@GetMapping("/{page}/{limit}")
public Result findPageQueryRole(@PathVariable("page") Long page, @PathVariable("limit") Long limit, SysRoleQueryVo sysRoleQueryVo){
//创建apge对象
System.out.println("page ==== " + page);
System.out.println("limit ==== " + limit);
// if(null == sysRoleQueryVo){
// sysRoleQueryVo = new SysRoleQueryVo();
sysRoleQueryVo.setRoleName("用户");
// }
System.out.println("========================" + sysRoleQueryVo.getRoleName() + "=========================");
Page pageParam = new Page<>(page, limit);
IPage pageModel = sysRoleService.mySelectPage(pageParam, sysRoleQueryVo);
System.out.println(pageModel.getRecords());
return Result.ok(pageModel);
}
- 全局异常处理器配置
package com.xxz.system.exception;
import com.xxz.common.result.Result;
import com.xxz.common.result.ResultCodeEnum;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 全局异常处理器-通过aop方式
*/
@ControllerAdvice
public class GlobalExcetionHandler {
//1.全局异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
return Result.fail().message("执行了全局异常处理");
}
//2.特定异常
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result error(ArithmeticException e){
return Result.fail().message("执行了特定异常处理");
}
//3.自定义异常
@ExceptionHandler(GuiguException.class)
@ResponseBody
public Result error(GuiguException e){
return Result.fail().message("执行了自定义异常处理");
}
/**
* spring security的异常:[要导security包] import org.springframework.security.access.AccessDeniedException;
* @param e
* @return
*/
//[new 权限]
//4.无法访问异常
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Result error(AccessDeniedException e){
return Result.fail().code(ResultCodeEnum.PERMISSION.getCode()).message("没有当前功能操作权限");
}
}