IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring Security配置 -> 正文阅读

[Java知识库]Spring Security配置

作者:token annotation punctuation

Spring Security配置

  • 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配置文件)
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类以及数据库查询类很简单就不写了
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-29 18:51:47  更:2022-06-29 18:53:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/20 19:13:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码