SpringBoot2.7.X整合SpringSecurity+JWT(入门级简单易懂)

前言

主要是自己想学习一下,也分享给大家,主要参考 链接: link
在这里我不会去介绍SpringSecurity 和JWT,直接上代码的形式,不了解的可以去了解一下
已经更新了,代码已完整、可以正常运行
有问题可以留言,对你有帮助请点个赞,让更多人学习到。

思路

1、先整合security:
使用自己数据库的用户,来登录认证(说一下我没有用到角色权限)
2、整合jwt:
能登录认证了,那就需要生成token
3、过滤器Filter:
每次调用接口时认证token合法性
4、jwt刷新机制:
我用的是缓存刷新机制,我这里弄的比较简单用的是SpringBoot自带的缓存
5、扩展:
比如token生成使用的秘钥可以使用RAS私钥公钥,提高安全性。缓存可以使用redis
在这里我就不使用了。

导入相关依赖

主要的依赖是 spring-boot-starter-web、spring-boot-starter-security、jjwt

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- 常用工具类 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

<!-- json工具 -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.7</version>
</dependency>

<!-- log4日志 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.7.9</version>
</dependency>

<!-- JJWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

整合security

建用户表

直接建立一个简单的用户表

用户id用户名用户名
useridusernamepassword

实体类UserDetailsInfo

给用户表创建实体类,并实现UserDetails。
UserDetails是security加载用户信息的接口,可以用来身份认证和授权

public class UserDetailsInfo implements UserDetails {

    private Integer userid;

    private String username;

    private String password;

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }


    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    //判断用户是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /*
    *判断用户是否被锁定
    * 一般用于多次登录失败。
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /*
     *用于判断用户的凭证(密码)是否过期
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /*
    *判断用户是否被禁用
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

创建Mapper

用于访问用户表
1、UserDetailsMapper

@Repository
public interface UserDetailsMapper {
  UserDetailsInfo getUserInfoByUsername(String username);
}

2、UserDetailsMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stc.login.mapper.UserDetailsMapper">
    <resultMap id="BaseResultMap" type="com.stc.login.model.UserDetailsInfo">
        <id column="UserId" jdbcType="INTEGER" property="userid" />
        <result column="Username" jdbcType="NVARCHAR" property="username" />
        <result column="Password" jdbcType="NVARCHAR" property="password" />
    </resultMap>
    <sql id="Base_Column_List">
        UserId, Username, Password
    </sql>
    <select id="getUserInfoByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from STC_EmployeePassWord
        where Username = #{username,jdbcType=NVARCHAR}
    </select>

</mapper>

创建UserDetailsServiceImpl

UserDetailsServiceImpl 是一个实现了 UserDetailsService 接口的类,它可以用来自定义从数据库中获取用户信息的逻辑。
UserDetailsService 接口只有一个方法 loadUserByUsername ,它接收一个用户名作为参数,返回一个 UserDetails 对象,表示用户的核心信息,包括用户名、密码、权限等。
我这里密码是明码,所以把数据库密码取出来在加密的,不然你的安全机制使用了加密,数据库密码又不加密会认证失败的(passwordEncoder.encode)

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserDetailsMapper userDetailsMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        UserDetailsInfo userDetailsInfo = userDetailsMapper.getUserInfoByUsername(username);
        userDetailsInfo.setPassword( passwordEncoder.encode(userDetailsInfo.getPassword()));
        if(userDetailsInfo == null){
            throw  new UsernameNotFoundException("用戶不存在");
        }

        return userDetailsInfo;
    }

}

配置 Security

SecurityConfig 是一个配置类,它可以用来自定义 Spring Security 的配置,包括认证、授权、跨域、异常处理等。
注意:spring boot2.7以上版本不需要继承WebSecurityConfigurerAdapter 类,直接使用@Bean注解
注意2:我这里就直接把完整的代码贴上来,已经加上了jwtAuthenticationFilter 和异常处理

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JWTAuthenticationFilter jwtAuthenticationFilter;

    @Autowired
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;

    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    /**
     * 密码明文加密方式配置
     *默认加密方式
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 获取AuthenticationManager(认证管理器),登录时认证使用
     * 默认认证
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.csrf().disable(); // 基于 token,不需要 csrf
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);// 基于 token,不需要 session
        // 下面开始设置权限
        http.authorizeRequests(authorize -> authorize
                        .antMatchers("/stc/login/login.action").permitAll()不需要进行身份验证的接口
                        .anyRequest().authenticated()//除上面外的所有请求全部需要 鉴权认证
                );

        //定义filter的先后顺序,保证 jwtFilter比用户验证的过滤器先执行
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        //自定义异常捕获机制
        http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint).accessDeniedHandler(myAccessDeniedHandler);
        return http.build();
    }
}

写一个登录接口进行测试

1、LoginAction

@RestController /* @RestController 是 @ResponseBody 和 @Controller 的组合注解 */
@RequestMapping("/stc/login")
public class LoginAction {
    @Autowired
    private LoginService loginService;
    
    @RequestMapping(value = {"login.action"}, method = RequestMethod.POST)
    public Map<String, String> login(@RequestBody JSONObject jsonObject) {
        return loginService.login(jsonObject);
    }
}

2、LoginService

public interface LoginService {
    public Map<String, String> login(JSONObject jsonObject);
}

3、LoginServiceImp

@Service
public class LoginServiceImp implements LoginService {
    private static final Logger log = LoggerFactory.getLogger(LoginServiceImp.class);
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserTokenCacheService userTokenCacheService;
     @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public Map<String, String> login(JSONObject jsonObject) {

        Map<String, String> loginMap = new HashMap<>();

        //封装 Authentication
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jsonObject.get("username"), jsonObject.get("password"));
        //认证用户
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        if (authenticate == null) {
            throw new RunException("认证失败!");
        }
        //获取认证用户信息
        UserDetailsInfo userDetailsInfo = (UserDetailsInfo) authenticate.getPrincipal();
        //认证通过,生成jwt
        String token = jwtTokenUtil.generateToken(userDetailsInfo);
        loginMap.put("userid", String.valueOf(userDetailsInfo.getUserid()));
        loginMap.put("username", userDetailsInfo.getUsername());
        loginMap.put("token", token);
        //存入缓存中
        userTokenCacheService.userTokenCachePut(loginMap);
        return loginMap;
    }
}

我已经把完整的代码贴出来了,包含了生成token,存入缓存,把生成好的token返回给客户端端,其他的接口就直接token认证

整合jwt

创建jwt工具类

@Component
public class JwtTokenUtil {
    private static final Logger log = LoggerFactory.getLogger(JwtTokenUtil.class);
    public static final String TOKEN_HEADER = "x-access-token"; //token请求头
    public static final String TOKEN_PREFIX = "Bearer";//token前缀
    private static final String ISSUER = "STC"; //发行方
    private static final String SUBJECT = "OMM"; //签名主题
    private static final String AUDIENCE = "OMM-VUE"; //接收方
    private String ROLE_CLAIMS = "role"; //角色权限声明
    private String secret = "secret"; //jwt加解密使用的密钥;
    private int expiration = 60; //jwt过期时间 秒级别

    private static final String CLAIM_KEY_USERNAME = "username";//登录的用户号
    private static final String CLAIM_KEY_CREATED = "created";// jwt创建时间


    /**
     * 生成token的过期时间
     */
    private Date generateExpirationDate() {
        //毫秒级
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 根据用户信息生成token
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());//用户名
        claims.put(CLAIM_KEY_CREATED, new Date());//创建日期

        return Jwts.builder()
                .setIssuer(ISSUER)//发行方
                .setSubject(SUBJECT)//主题
                .setAudience(AUDIENCE)//接收方
                .setClaims(claims)//其他信息,主要是用户信息
                .setIssuedAt(new Date())//签发日期
                .setNotBefore(new Date())//生效日期
                .setExpiration(generateExpirationDate())//失效时间
                .signWith(SignatureAlgorithm.HS512, secret)//生成算法
                .compact();
    }

    /**
     * 从token中获取JWT中的有效载荷
     */
    public Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            // 捕获过期异常
            log.info("token:" + token);
            log.info("token已经过期:" + e.getMessage());
            claims = e.getClaims();
        } catch (Exception e) {
            log.info("token:" + token);
            log.info("JWT格式验证失败:" + e.getMessage());
        }
        return claims;
    }

    /**
     * 从token中获取登录用户名
     */
    public String getUserNameFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.get("username").toString();
    }

    /**
     * 从token中获取过期时间
     */
    public Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }


    /**
     * 判断token是否已经失效
     */
    public boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }
}

创建jwt过滤器

JWTAuthenticationFilter 是一个自定义的过滤器类,它继承了 OncePerRequestFilter 类,用于实现 JWT 的验证机制。

JWTAuthenticationFilter 需要重写 doFilterInternal 方法,用于检查请求头中是否有 Authorization 字段,并验证 JWT 的正确性,然后将认证信息设置到 SecurityContext 中

JWTAuthenticationFilter 需要在 SecurityConfig 中通过 addFilterBefore 方法添加到过滤器链中,并指定在 UsernamePasswordAuthenticationFilter 之前执行。

@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserTokenCacheService userTokenCacheService;

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //需要认证token的接口才进入
        if(!notNeedFilter(request)){
            // 获取 token
            String token = request.getHeader(jwtTokenUtil.TOKEN_HEADER);
            if (StringUtils.hasText(token)) {
                // 解析token,获取 username
                String username = jwtTokenUtil.getUserNameFromToken(token);
                //判断是否有该username 的缓存,(登录存入缓存,注销清除缓存)
                userTokenCacheInfo userTokenCacheInfo = userTokenCacheService.getUserTokenCacheById(username);
                if (userTokenCacheInfo != null) {
                    //判断存储的token跟传入的token是否一致,
                    String CToken = userTokenCacheInfo.getToken();
                    if (CToken.equals(token)) {

                        //判断token是否过期
                        if (jwtTokenUtil.isTokenExpired(token)) {
                            //判断是否超过最大失效时间
                            if (userTokenCacheInfo.getMaxExpirationDate().before(new Date())) {
                                ResponseProcessing(request, response, "exceeding-maximum-failure-time", null);
                            }
                            //是否超过设定时间没有操作
                            else if (DateUtils.addMinutes(userTokenCacheInfo.getOldOperationDate(), userTokenCacheInfo.getInactiveMinute()).before(new Date())) {
                                ResponseProcessing(request, response, "exceeding-inactive-time", null);
                            }
                            //满足条件 活跃用户,又没有超过最大失效时间,从新生成token给客户端
                            else {
                                UserDetailsInfo userDetailsInfo = new UserDetailsInfo();
                                userDetailsInfo.setUsername(username);
                                //生成jwt
                                String newToken = jwtTokenUtil.generateToken(userDetailsInfo);
                                //更新缓存
                                userTokenCacheService.userTokenCacheUpdate(newToken, username);
                                ResponseProcessing(request, response, "renovate-token", newToken);
                            }
                        } else {
                            //用户是否授权
                            if (SecurityContextHolder.getContext().getAuthentication() == null) {
                                //将User 封装到 securityContextHolder。 封装到securityContextHolder以后,其他过滤器就不会在拦截
                                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userTokenCacheInfo, null, null);
                                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                            }
                            //认证成功,更改缓存里面的调用接口时间,主要用于判断是否活跃
                            userTokenCacheService.userTokenCacheOldOperationDateUpdate(userTokenCacheInfo, username);
                        }
                    }
                }
            }
        }
        filterChain.doFilter(request, response);
    }

    //直接返回给客户端
    private void ResponseProcessing(HttpServletRequest request, HttpServletResponse response, String type, String token) throws IOException {
        Map<String, Object> map = new HashMap<>();
        map.put("uri", request.getRequestURI());
        if (type.equals("exceeding-maximum-failure-time")) {
            map.put("msg", "exceeding-maximum-failure-time");
        } else if (type.equals("exceeding-inactive-time")) {
            map.put("msg", "exceeding-inactive-time");
        } else if (type.equals("renovate-token")) {
            map.put("msg", "renovate-token");
            map.put("token", token);
        } else {
            map.put("msg", "身份认证失败");
        }
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        String resBody = JSON.toJSONString(map);
        PrintWriter printWriter = response.getWriter();
        printWriter.print(resBody);
        printWriter.flush();
        printWriter.close();
    }


    /**
     * 判断是否可以通过过滤
     */
    private boolean notNeedFilter(HttpServletRequest request) {
        //登录接口
        if ((request.getContextPath() + "/stc/login/login.action").equals(request.getRequestURI())) {
            return true;
        }
        return false;
    }

}

我已经把完整的代码贴出,使用到了缓存
我这里的token 使用的是缓存刷新token机制
第一点:如果超过最大的失效日期,需要客户端重新输入用户名和密码认证,发放新的token
第二点:我设置是30分钟不活跃,需要客户端重新输入用户名和密码认证,发放新的token
第三点:不执行上面两点,token过期直接发放新的token,然后客户端获取token重新发送请求。

使用自带缓存用于token刷新机制

包含登录成功调用缓存,更新调用缓存,更新活跃时间调用缓存,注销删除缓存,
Spring boot 自带缓存可以去了解一下。

1、userTokenCacheInfo

public class userTokenCacheInfo {
    private String token;
    private String username;
    private Date loginDate;//登录时间
    private Date notBeforeDate; //token生效时间
    private Date expirationDate; //token过期时间
    private int refreshCount = 0; //token的刷新次数
    private Date maxExpirationDate;//token最大的失效时间
    private Date OldOperationDate;//上一次调用接口的操作时间
    private String equipmentIP;//设备ip
    private int InactiveMinute = 30; //默认30分钟,用于如果30分钟不操作,token失效。

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getLoginDate() {
        return loginDate;
    }

    public void setLoginDate(Date loginDate) {
        this.loginDate = loginDate;
    }

    public Date getNotBeforeDate() {
        return notBeforeDate;
    }

    public void setNotBeforeDate(Date notBeforeDate) {
        this.notBeforeDate = notBeforeDate;
    }

    public Date getExpirationDate() {
        return expirationDate;
    }

    public void setExpirationDate(Date expirationDate) {
        this.expirationDate = expirationDate;
    }

    public int getRefreshCount() {
        return refreshCount;
    }

    public void setRefreshCount(int refreshCount) {
        this.refreshCount = refreshCount;
    }

    public Date getMaxExpirationDate() {
        return maxExpirationDate;
    }

    public void setMaxExpirationDate(Date maxExpirationDate) {
        this.maxExpirationDate = maxExpirationDate;
    }

    public Date getOldOperationDate() {
        return OldOperationDate;
    }

    public void setOldOperationDate(Date oldOperationDate) {
        OldOperationDate = oldOperationDate;
    }

    public String getEquipmentIP() {
        return equipmentIP;
    }

    public void setEquipmentIP(String equipmentIP) {
        this.equipmentIP = equipmentIP;
    }

    public int getInactiveMinute() {
        return InactiveMinute;
    }

    public void setInactiveMinute(int inactiveMinute) {
        InactiveMinute = inactiveMinute;
    }


}

2、UserTokenCacheService

public interface UserTokenCacheService {
    public void userTokenCachePut(Map<String, String> loginMap);

    public userTokenCacheInfo getUserTokenCacheById(Object key);

    public void userTokenCacheUpdate(String token, Object key);

    public void userTokenCacheOldOperationDateUpdate(userTokenCacheInfo userTokenCacheInfo, Object key);

    public void userTokenCacheEvict(Object key);

}

3、UserTokenCacheServiceImpl

@Service
public class UserTokenCacheServiceImpl implements UserTokenCacheService {
    // 注入缓存管理器
    @Autowired
    private CacheManager cacheManager;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    // 定义userTokenCache()方法,用于获取或创建"userTokenCache"缓存对象
    //用于Token缓存
    private Cache userTokenCache() {
        // 获取或创建名为"userCache"的缓存对象
        Cache cache = cacheManager.getCache("userTokenCache");
        return cache;
    }

    // 从缓存中获取用户数据
    @Override
    public userTokenCacheInfo getUserTokenCacheById(Object key) {
        // 获取"userCache"缓存对象
        Cache cache = userTokenCache();
        // 从缓存中获取用户数据
        userTokenCacheInfo userTokenCacheInfo = cache.get(key, userTokenCacheInfo.class);
        return userTokenCacheInfo;
    }

    //初始化放入缓存中
    @Override
    public void userTokenCachePut(Map<String, String> loginMap) {
        // 获取"userCache"缓存对象
        Cache cache = userTokenCache();
        // 从缓存中获取用户数据
        userTokenCacheInfo userTokenCacheInfo = getUserTokenCacheById(loginMap.get("username"));
        if (userTokenCacheInfo == null) {
            userTokenCacheInfo = new userTokenCacheInfo();
        }
        userTokenCacheInfo.setUsername(loginMap.get("username"));
        userTokenCacheInfo.setToken(loginMap.get("token"));
        //获取token的信息
        Claims claims = jwtTokenUtil.getClaimsFromToken(loginMap.get("token"));
        userTokenCacheInfo.setLoginDate(claims.getNotBefore());//登录日期
        userTokenCacheInfo.setNotBeforeDate(claims.getNotBefore());//生效日期
        userTokenCacheInfo.setExpirationDate(claims.getExpiration());//失效日期
        userTokenCacheInfo.setRefreshCount(0);//刷新次数
        userTokenCacheInfo.setOldOperationDate(claims.getNotBefore());//上一次调用接口的操作时间
        userTokenCacheInfo.setMaxExpirationDate(DateUtils.addHours(claims.getNotBefore(), 4));//最大失效时间

        // 将用户数据放入缓存中
        cache.put(loginMap.get("username"), userTokenCacheInfo);
    }


    //更新缓存token
    @Override
    public void userTokenCacheUpdate(String token, Object key) {

        // 从缓存中获取用户数据
        userTokenCacheInfo userTokenCacheInfo = getUserTokenCacheById(key);
        //获取token的信息
        Claims claims = jwtTokenUtil.getClaimsFromToken(token);
        userTokenCacheInfo.setToken(token);
        userTokenCacheInfo.setNotBeforeDate(claims.getNotBefore());//生效日期
        userTokenCacheInfo.setExpirationDate(claims.getExpiration());//失效日期
        userTokenCacheInfo.setRefreshCount(userTokenCacheInfo.getRefreshCount() + 1);//刷新次数
        userTokenCacheInfo.setOldOperationDate(new Date());//上一次调用接口的操作时间
        // 获取"userCache"缓存对象
        Cache cache = userTokenCache();
        // 将用户数据放入缓存中
        cache.put(key, userTokenCacheInfo);

    }

    //更新缓存 调用接口的操作时间
    @Override
    public void userTokenCacheOldOperationDateUpdate(userTokenCacheInfo userTokenCacheInfo, Object key) {
        userTokenCacheInfo.setOldOperationDate(new Date());//上一次调用接口的操作时间
        // 获取"userCache"缓存对象
        Cache cache = userTokenCache();
        // 将用户数据放入缓存中
        cache.put(key, userTokenCacheInfo);

    }
    //删除缓存
    @Override
    public void userTokenCacheEvict(Object key) {
        // 获取"userCache"缓存对象
        Cache cache = userTokenCache();
        cache.evict(key);
    }
}

两个异常类

1、MyAuthenticationEntryPoint

@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

        Map<String, Object> map = new HashMap<>();
        map.put("uri", httpServletRequest.getRequestURI());
        map.put("msg", "身份认证失败");//身份认证失败
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        ObjectMapper objectMapper = new ObjectMapper();
        String resBody = objectMapper.writeValueAsString(map);
        PrintWriter printWriter = httpServletResponse.getWriter();
        printWriter.print(resBody);
        printWriter.flush();
        printWriter.close();
    }
}

2、MyAccessDeniedHandler

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        Map<String, Object> map = new HashMap<>();
        map.put("uri",httpServletRequest.getRequestURI());
        map.put("msg","鉴权失败");//没有足够的权限访问该资源
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        ObjectMapper objectMapper = new ObjectMapper();
        String resBody = objectMapper.writeValueAsString(map);
        PrintWriter printWriter = httpServletResponse.getWriter();
        printWriter.print(resBody);
        printWriter.flush();
        printWriter.close();
    }
}

测试接口

1、登录接口测试获取token
在这里插入图片描述

2、缓存机制测试 这里我就直接拿我的客户端测试了
没有token、或者跟缓存的token不一样、直接访问,进入异常类返回身份认证失败
在这里插入图片描述3、token过期了,重新生成token
在这里插入图片描述

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring Boot 2.7 还没有发布,可能是您想说的是 Spring Boot 2.4。 在 Spring Boot 2.4 中,配置 Spring SecurityJWT 的步骤如下: 1. 添加依赖 在 pom.xml 文件中添加 Spring SecurityJWT 的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.2</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> ``` 2. 配置 Spring SecuritySpring Security 的配置类中,添加以下代码: ```java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtTokenFilter jwtTokenFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/authenticate").permitAll() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password(passwordEncoder().encode("password")).roles("USER"); } } ``` 这个配置类中,我们首先禁用了 CSRF 防护,然后配置了访问控制,允许 `/api/authenticate` 路径的请求不需要认证,其他路径的请求需要进行认证。同时,我们还配置了使用 JWT 进行认证,通过 `JwtTokenFilter` 过滤器来实现。 3. 配置 JWTJWT 的配置类中,添加以下代码: ```java @Configuration public class JwtConfig { @Value("${jwt.secret}") private String secret; @Bean public JwtParser jwtParser() { return Jwts.parser().setSigningKey(secret); } @Bean public JwtEncoder jwtEncoder() { return Jwts.builder().setSigningKey(secret).build(); } @Bean public JwtTokenFilter jwtTokenFilter() { return new JwtTokenFilter(jwtParser()); } } ``` 在这个配置类中,我们首先从配置文件中读取了 JWT 的密钥,然后配置了 `JwtParser` 和 `JwtEncoder` 对象,用于解析和生成 JWT。最后,我们还配置了 `JwtTokenFilter` 过滤器。这个过滤器用于在每个请求中解析 JWT,并将解析后的信息传递给 Spring Security 进行认证。 4. 编写 JwtTokenFilter 最后,我们需要编写 `JwtTokenFilter` 过滤器。这个过滤器用于在每个请求中解析 JWT,并将解析后的信息传递给 Spring Security 进行认证。具体代码如下: ```java public class JwtTokenFilter extends OncePerRequestFilter { private final JwtParser jwtParser; public JwtTokenFilter(JwtParser jwtParser) { this.jwtParser = jwtParser; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); try { Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token); String username = claimsJws.getBody().getSubject(); List<SimpleGrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } catch (Exception e) { SecurityContextHolder.clearContext(); } } chain.doFilter(request, response); } } ``` 在这个过滤器中,我们首先从请求头中获取 JWT,然后使用 `JwtParser` 对象对 JWT 进行解析。如果解析成功,我们就从 JWT 中获取用户名,并创建一个 `UsernamePasswordAuthenticationToken` 对象,用于在 Spring Security 中进行认证。最后,我们将认证信息保存到 `SecurityContextHolder` 中,以便于后续的处理。如果解析失败,我们就清空 `SecurityContextHolder` 中的认证信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值