SpringSecurity+jwt使用

参考文章链接

自定义SpringSecurity用户

package com.daben.springsecurityjwt.vo;

import com.daben.springsecurityjwt.entity.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
/**
 * SpringSecurity用户
 *
 * @Author JinDongSheng
 * @Date 2023-11-16 09:21
 */
public class SpringSecurityUser extends User {
    /**
     * 系统用户
     */
    private SysUser sysUser;
    /**
     * 构造函数
     */
    public SpringSecurityUser(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;
    }
}

自定义认证失败处理类

package com.daben.springsecurityjwt.handle;

import com.alibaba.fastjson2.JSON;
import com.daben.springsecurityjwt.vo.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * 未登录处理类
 *
 * @Author JinDongSheng
 * @Date 2023-11-15 17:37
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(JSON.toJSONString(Result.error("认证失败,无法访问系统资源!")));
    }
}

自定义Security用户服务实现类

package com.daben.springsecurityjwt.service;

import com.daben.springsecurityjwt.entity.SysUser;
import com.daben.springsecurityjwt.vo.SpringSecurityUser;
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;
import java.util.Collections;
/**
 * Security用户服务实现类
 *
 * @Author JinDongSheng
 * @Date 2023-11-16 09:14
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private SysUserServiceImpl sysUserServiceImpl;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名称查询用户
        SysUser sysUser = sysUserServiceImpl.selectSysUserByName(username);
        // 查询用户权限
        List<GrantedAuthority> authorities = Collections.emptyList();
        // 返回Security指定的用户对象
        return new SpringSecurityUser(sysUser, authorities);
    }
}

自定义JWT工具类

package com.daben.springsecurityjwt.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.daben.springsecurityjwt.entity.SysUser;
import io.jsonwebtoken.*;
import org.apache.commons.lang3.StringUtils;
import java.util.Date;
/**
 * JWT工具类
 *
 * @Author JinDongSheng
 * @Date 2023-11-16 15:49
 */
public class JwtUtil {
    /**
     * Token有效期为30分钟
     */
    public static final int EXPIRE_TIME = 30;
    /**
     * 秘钥
     */
    private final static String SECRET_KEY = "ABCDE";
    /**
     * 生成token
     */
    public static String createToken(SysUser user) {
        return Jwts.builder()
                // 将user放进JWT
                .setSubject(JSONUtil.toJsonStr(user))
                // 设置过期时间
                .setExpiration(DateUtil.offsetMinute(new Date(), EXPIRE_TIME))
                // 设置加密算法和秘钥
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
    public static Claims parseToken(String token) {
        // 如果是空字符串直接返回null
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        // 这个Claims对象包含了许多属性,比如签发时间、过期时间以及存放的数据等
        Claims claims = null;
        // 解析失败了会抛出异常,所以我们要捕捉一下。token过期、token非法都会导致解析失败
        try {
            claims = Jwts.parser()
                    .setSigningKey(SECRET_KEY) // 设置秘钥
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException e) {
            // 这里应该用日志输出,为了演示方便就直接打印了
            System.err.println("解析失败!");
        }
        return claims;
    }
}

自定义token认证过滤器

package com.daben.springsecurityjwt.filter;

import cn.hutool.json.JSONUtil;
import com.daben.springsecurityjwt.entity.SysUser;
import com.daben.springsecurityjwt.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
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;
/**
 * 登录过滤器
 *
 * @Author JinDongSheng
 * @Date 2023-11-16 14:48
 */
@Component
public class LoginFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 解析token
        Claims claims = JwtUtil.parseToken(request.getHeader("Authorization"));
        if (claims != null) {
            // 获取用户
            SysUser sysUser = JSONUtil.toBean(claims.getSubject(), SysUser.class);
            // 手动组装一个认证对象
            Authentication authentication = new UsernamePasswordAuthenticationToken(sysUser, null, Collections.emptyList());
            // 将认证对象放到上下文中
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }
}

自定义SpringSecurity配置类

package com.daben.springsecurityjwt.config;

import com.daben.springsecurityjwt.filter.LoginFilter;
import com.daben.springsecurityjwt.handle.AuthenticationEntryPointImpl;
import com.daben.springsecurityjwt.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
 * spring security 配置
 *
 * @Author JinDongSheng
 * @Date 2023-11-15 17:16
 */
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 登录认证过滤器
     */
    @Autowired
    private LoginFilter loginFilter;
    /**
     * Security用户服务实现类
     */
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    /**
     * 未登录处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl authenticationEntryPoint;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf
        http.csrf().disable();
        // 关闭frameOptions
        http.headers().frameOptions().disable();
        // 指定未登录处理类
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        // 前置登录认证过滤器
        http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);
        // 禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 配置认证和授权接口
        http.authorizeRequests()
                // 指出放行接口
                .antMatchers("/sys/port/*").permitAll()
                // 拦截剩余接口
                .anyRequest().authenticated();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用bcrypt加密
        return new BCryptPasswordEncoder();
    }
}

登录入口

package com.daben.springsecurityjwt.controller;

import com.daben.springsecurityjwt.entity.SysUser;
import com.daben.springsecurityjwt.utils.JwtUtil;
import com.daben.springsecurityjwt.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 登录控制器
 *
 * @Author JinDongSheng
 * @Date 2023-11-15 17:25
 */
@RestController
@RequestMapping("/sys/port")
public class SysPortController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @PostMapping("/login")
    public Result login(@RequestBody SysUser sysUser) {
        // 认证
        Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword()));
        if (authenticate.isAuthenticated()) {
            // 生成token
            String token = JwtUtil.createToken(sysUser);
            // 返回token
            return Result.success(token);
        }
        return Result.error("用户名或密码错误");
    }
}

整体调用流程

  1. 服务启动,根据【自定义SpringSecurity配置类】配置信息(指定放行和认证接口、token认证过滤器、认证失败处理器、密码加密器、用户查询服务等。)
  2. 用户发起登录请求。
  3. token认证过滤器过滤,因为不携带token,直接放行调用登录接口,在登录接口中手动调用AuthenticationManager进行用户信息认证。
  4. AuthenticationManager进行用户信息认证时会调用【自定义Security用户服务实现类】查询数据库用户,然后将数据库用户封装成Security指定用户返回。
  5. 认证失败调用【自定义认证失败处理类】,认证成功则返回token
  6. 用户发起非登录请求,token过滤器过滤,token校验成功将认证对象放入上下文中。调用请求接口。token校验失败,调用【自定义认证失败处理类】。

pom文件


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>com.vitalframeworkgroupId>
        <artifactId>vital-framework-dependenciesartifactId>
        <version>1.1.0version>
    parent>
    <groupId>com.citicgroupId>
    <artifactId>vital-portal-coreartifactId>
    <version>1.0.0version>
    <name>vital-portal-corename>
    <description>系统管理(门面工程)模块description>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.rep4orting.outputEncoding>UTF-8project.rep4orting.outputEncoding>
        <java.version>1.8java.version>
        <maven.compiler.source>${java.version}maven.compiler.source>
        <maven.compiler.target>${java.version}maven.compiler.target>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>com.vitalframework.webgroupId>
            <artifactId>vital-framework-web-commonartifactId>
            <version>1.0.0version>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>easyexcelartifactId>
        dependency>
        <dependency>
            <groupId>commons-netgroupId>
            <artifactId>commons-netartifactId>
        dependency>
        <dependency>
            <groupId>com.jcraftgroupId>
            <artifactId>jschartifactId>
        dependency>
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-generatorartifactId>
        dependency>

        <dependency>
            <groupId>org.apache.poigroupId>
            <artifactId>poiartifactId>
        dependency>

        <dependency>
            <groupId>org.apache.poigroupId>
            <artifactId>poi-ooxml-schemasartifactId>
        dependency>

        <dependency>
            <groupId>org.apache.poigroupId>
            <artifactId>poi-scratchpadartifactId>
        dependency>

        <dependency>
            <groupId>org.codehaus.castorgroupId>
            <artifactId>castor-xmlartifactId>
        dependency>
    dependencies>

    <build>

        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <fork>truefork>
                    <skip>trueskip>
                configuration>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-source-pluginartifactId>
                <version>${maven-source-plugin.version}version>
            plugin>
        plugins>
    build>
project>

你可能感兴趣的:(java)