SpringSecurity集成JWT实现后端认证授权保姆级教程-认证配置篇

70 篇文章 3 订阅
58 篇文章 3 订阅

🍁 作者:知识浅谈,CSDN签约讲师,CSDN博客专家,华为云云享专家,阿里云专家博主
📌 擅长领域:全栈工程师、爬虫、ACM算法
💒 公众号:知识浅谈
🔥 微信:zsqtcyl 联系我领取福利

视频教程:SpringSecurity集成JWT实现后端认证授权保姆级教程-认证配置
完整代码:SecurityJwt
上一篇:SpringSecurity集成JWT实现后端认证授权保姆级教程-工具类准备篇
下一篇:SpringSecurity集成JWT实现后端认证授权保姆级教程-授权配置篇
🤞上边的各种配置都完成之后,本节开始进行SpringSecurity的认证🤞

🎈用户类继承UserDetails

为什么要继承这个UserDetails类呢,因为后续SpringSecurity中进行认证授权都是通过实现UserDetail接口和UserDetailsService

针对数据准备篇的CustUser实体类进行更改,实现UserDetails 并实现其对应的方法

@TableName(value ="cust_user")
@Data
public class CustUser implements Serializable, UserDetails {  //这里新增实现UserDetails 
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String username;
    private String nickname;
    private Integer enable;
    private String password;
//----------------------------------------------------以下为新增的部分---------------------------------
    @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;
    }
    //------------------------------------------------------------------------------------------------
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

🎈用户Service实现UserDetailService

实现UserDetailService并实现其对应的loadUserByUsername

  1. 针对之前的数据准备篇的CustUserService接口 进行更改

    public interface CustUserService extends IService<CustUser>, UserDetailsService {  //这个地方新增继承这个UserDetailsService接口 
    }
    
  2. 针对之前的数据准备篇的CustUserServiceImpl实现类 进行更改

    @Service
    public class CustUserServiceImpl extends ServiceImpl<CustUserMapper, CustUser> implements CustUserService{ //这个地方新增实现CustUserService接口
    //-----------------------------------------------------以下为新增的内容------------------------------------------
        @Autowired
        private CustUserMapper custUserMapper;
    
        @Autowired
        private SysMenuMapper menuMapper;
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            LambdaQueryWrapper<CustUser> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(CustUser::getUsername, username);
            CustUser user = custUserMapper.selectOne(queryWrapper);
            if (user == null) {
                log.error("用户名不存在");
                throw new UsernameNotFoundException("用户名不存在");
            }else {
                return user;
            }
        }
    //----------------------------------------------------------------------------
    }
    

    上边的这个主要是实现loadUserByUsername方法在SpringSecurity认证的过程中使用的到。

🎈SecurityConfig配置类重要

在config文件夹下新增Security配置类,这类用于指定拦截路径、设置登录退出接口,异常处理等,直接上代码

package com.example.demo.config;

import com.example.demo.common.Vo.Result;
import com.example.demo.service.CustUserService;
import com.example.demo.utils.JwtUtil;
import com.google.gson.Gson;

import lombok.RequiredArgsConstructor;
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.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Slf4j
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法安全权限校验
public class SecurityConfiguration {

    private final JwtUtil jwtUtil; // 注入JwtUtil
    @Autowired
    private CustUserService custUserService;
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
         return new BCryptPasswordEncoder();
    }

    /**
     * 获取AuthenticationManager 登录验证的时候使用
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         http.authorizeHttpRequests(
                (authz)->authz
                        .antMatchers("/user/login").permitAll() // 允许匿名用户访问login用于登录
                        .antMatchers("/user/logout").permitAll() // 允许匿名用户访问logout用于登出
                        .antMatchers("/doc.html", "/webjars/**", "/v2/api-docs", "/swagger-resources/**").permitAll() // 允许匿名用户访问swagger
                        .antMatchers("/test").hasAuthority("test") // 拥有test权限的用户才能访问test接口
                        .anyRequest().authenticated()) // 其他请求必须经过身份验证
                .exceptionHandling(conf->conf // 异常处理
                        .authenticationEntryPoint((req, res, authException) -> { //认证异常
                            log.info("认证异常");
                            res.setContentType( "application/json;charset=utf-8" );
                            res.getWriter().write(new Gson().toJson(Result.error(401, authException.getMessage())));
                        })
                        .accessDeniedHandler((req, res, authException) -> { //权限异常
                            log.info("权限异常");
                            res.setContentType("application/json;charset=utf-8");
                            res.getWriter().write(new Gson().toJson(Result.error(403, authException.getMessage())));
                        })
                )
                .csrf(AbstractHttpConfigurer::disable) // 禁用csrf
                .sessionManagement(conf->conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 禁用session
//                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)//指定过滤器
                .cors();
        return http.build();
    }
}

🎈编写登录成功后返回的带有token的实体类

在这里插入图片描述

package com.example.demo.Vo;


import com.example.demo.domain.CustUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthUserVo extends CustUser {
    private String token;
}

🎈编写用户登录退出接口

现在我们要实现的是编写登录接口实现用户登录返回token,上边的配置类放行了/user/login 用作登录,本次是编写后端登自定义登录模块,所以不使用SpringSecurity自带的表单登录。

在这里插入图片描述

package com.example.demo.controller;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.demo.Vo.AuthUserVo;
import com.example.demo.common.Vo.Result;
import com.example.demo.domain.CustUser;
import com.example.demo.utils.JwtUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
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.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
import static com.example.demo.common.constants.OtherConstants.AUTH_TOKEN;
import static com.example.demo.common.constants.OtherConstants.USER_PREFIX;


@Api(tags = "用户模块")
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private AuthenticationManager authenticateManager;
    @Autowired
    private JwtUtil jwtUtil;
    @ApiOperation(value = "用户登录")
    @PostMapping("/login")
    public Result login(@RequestBody CustUser user) {
        Result result = new Result();
        try {
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
            Authentication authenticate = authenticateManager.authenticate(usernamePasswordAuthenticationToken);
            if (authenticate == null) {
                return Result.error(401, "登录校验失败");
            } else {
                user = (CustUser) authenticate.getPrincipal();
                String token = jwtUtil.createJwt(user);//创建token
                AuthUserVo authUserVo = new AuthUserVo();
                BeanUtils.copyProperties(user, authUserVo);
                authUserVo.setToken(token);
    //            将token放在redis中
                redisTemplate.opsForValue().set(USER_PREFIX + String.valueOf(user.getId()),user,30, TimeUnit.MINUTES);
                return Result.success(authUserVo);
            }
        } catch (Exception e) {
            return result.error500("登陆失败");
        }
    }


    @ApiOperation(value = "用户退出")
    @GetMapping("/logout")
    public Result logout(HttpServletRequest req) {
        String token = req.getHeader(AUTH_TOKEN);
        if (token == null || "".equals(token)) {
            return Result.error(401, "token为空");
        } else {
            DecodedJWT decodedJWT = jwtUtil.resolveJwt(token);
            CustUser user = jwtUtil.toUser(decodedJWT);
            redisTemplate.delete(USER_PREFIX + String.valueOf(user.getId()));
            return Result.success("退出成功");
        }
    }
}

🎈新增JwtAuthenticationFilter.java 拦截检查token是否有效

在这里插入图片描述

package com.example.demo.filter;

import com.auth0.jwt.interfaces.DecodedJWT;

import com.example.demo.domain.CustUser;
import com.example.demo.utils.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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.Objects;

import static com.example.demo.common.constants.OtherConstants.AUTH_TOKEN;


/**
 * @author: YinLei
 * Package:  com.zsqt.security.filter
 * @date: 2024/1/3 16:40
 * @Description:
 * @version: 1.0
 */

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader(AUTH_TOKEN);

        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }

        DecodedJWT jwt = jwtUtil.resolveJwt(token); //解析token
        if (jwt != null) {//如果token有效,将用户信息放入到SecurityContext中
            CustUser user = jwtUtil.toUser(jwt);
            if (Objects.isNull(user)) {
                throw new RuntimeException("用户未登录");
            }
            if (jwtUtil.checkRedis(user)) {// 验证redis中是否存在该用户
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); //这个方法将用户信息放入到SecurityContext中 手动设置用户信息
            }else {
                throw new RuntimeException("Token失效");
            }
        }else{
            throw new RuntimeException("Token失效");
        }
        filterChain.doFilter(request, response); // 如果token无效,进行下一个过滤器
    }
}

🎈登录接口测试

因为我们配置了swagger,所以我们直接在swagger测试登录,最后返回了token
swagger文档默认地址(上边的配置文件已经放行文档地址):http://localhost:8080/doc.html
在这里插入图片描述

到这认证就已经好了,接下来就要进行授权了。

🍚总结

大功告成,撒花致谢🎆🎇🌟,关注我不迷路,带你起飞带你富。
Writted By 知识浅谈

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识浅谈

您的支持将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值