spring security 前后端分离 进行用户验证 权限登陆的实现代码(看不懂??直接cv)

目录

目录

前言:

一.所需依赖

二.application.properties

 三.工具类

3.1ApplicationContextUtils

3.2JwtUtils

 3.3ResponseResult

3.4ResponseStatus 

3.5RsaUtils

四.UserDetailServiceImpl

五.成功处理器

 六.SecurityConfig

七. filter

八.项目结构


前言:

前后端分离项目运行流程

spring security 前后端分离 进行用户验证 权限登陆的实现代码(看不懂??直接cv)_第1张图片

我之前的博客写了一篇前后端不分离的权限验证,前后端分离就是在原来的基础上加上发送token给前端,前端发送请求给服务器需要携带token。不太懂的朋友请先移步(29条消息) springsecurity基于数据库中的用户信息实现登陆_代码大帝的博客-CSDN博客

嫌麻烦的同学也可以直接把配置代码拿来用,代码都有写的。

一.所需依赖



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.4.RELEASE
         
    
    com.dmdd
    javatestspringsecurity
    0.0.1-SNAPSHOT
    javatestspringsecurity
    javatestspringsecurity
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.security
            spring-security-test
            test
        

        
            org.projectlombok
            lombok
            true
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.5.2
        

        
            mysql
            mysql-connector-java
            runtime
        

        
            io.jsonwebtoken
            jjwt
            0.9.0
        

        
            joda-time
            joda-time
            2.9.9
        
        
            com.baomidou
            mybatis-plus-annotation
            3.5.2
            compile
        

        
            org.springframework.boot
            spring-boot-starter-data-redis
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


直接拿来用。

二.application.properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/edu_user?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=jly720609
mybatis-plus.type-aliases-package=com.dmdd.javatestspringsecurity.entity
mybatis-plus.mapper-locations=classpath:mapper/*.xml


懂的都懂

 三.工具类

包含生成公钥秘钥,对象转json等一系列方法

3.1ApplicationContextUtils

package com.dmdd.javatestspringsecurity.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 应用程序上下文工具
 * 程序启动后,会创建ApplicationContext对象
 * ApplicationContextAware能感知到ApplicationContext对象
 * 自动调用setApplicationContext方法
 */
@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    //系统的IOC容器
    private static ApplicationContext applicationContext = null;

    //感知到上下文后,自动调用,获得上下文
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtils.applicationContext = applicationContext;
    }

    //返回对象
    public static  T getBean(Class tClass){
        return applicationContext.getBean(tClass);
    }
}

3.2JwtUtils

package com.dmdd.javatestspringsecurity.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * JWT工具类
 */
public class JwtUtils {

    public static final String JWT_KEY_USERNAME = "username";
    public static final int EXPIRE_MINUTES = 120;

    /**
     * 私钥加密token
     */
    public static String generateToken(String username, PrivateKey privateKey, int expireMinutes) {

        return Jwts.builder()
                .claim(JWT_KEY_USERNAME, username)
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }

    /**
     * 从token解析用户
     *
     * @param token
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static String getUsernameFromToken(String token, PublicKey publicKey){
        Jws claimsJws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
        Claims body = claimsJws.getBody();
        String username = (String) body.get(JWT_KEY_USERNAME);
        return username;
    }
}

 3.3ResponseResult

package com.dmdd.smart_community.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 响应数据封装对象
 */
@Data
public class ResponseResult {

    /**
     * 状态信息
     */
    private ResponseStatus status;

    /**
     * 数据
     */
    private T data;

    public ResponseResult(ResponseStatus status, T data) {
        this.status = status;
        this.data = data;
    }

    /**
     * 返回成功对象
     * @param data
     * @return
     */
    public static   ResponseResult ok(T data){
        return new ResponseResult<>(ResponseStatus.OK, data);
    }

    /**
     * 返回错误对象
     * @param status
     * @return
     */
    public static ResponseResult error(ResponseStatus status){
        return new ResponseResult<>(status,status.getMessage());
    }

    /**
     * 返回错误对象
     * @param status
     * @return
     */
    public static ResponseResult error(ResponseStatus status,String msg){
        return new ResponseResult<>(status,msg);
    }

    /**
     * 向流中输出结果
     * @param resp
     * @param result
     * @throws IOException
     */
    public static void write(HttpServletResponse resp, ResponseResult result) throws IOException {
        //设置返回数据的格式
        resp.setContentType("application/json;charset=UTF-8");
        //jackson是JSON解析包,ObjectMapper用于解析 writeValueAsString 将Java对象转换为JSON字符串
        String msg = new ObjectMapper().writeValueAsString(result);
        //用流发送给前端
        resp.getWriter().print(msg);
        resp.getWriter().close();
    }
}

3.4ResponseStatus 

package com.dmdd.javatestspringsecurity.util;

/**
 * 响应状态枚举
 */
public enum ResponseStatus {
    /**
     * 内置状态
     */
    OK(200,"操作成功"),
    INTERNAL_ERROR(500000,"系统错误"),
    BUSINESS_ERROR(500001,"业务错误"),
    LOGIN_ERROR(500002,"账号或密码错误"),
    NO_DATA_ERROR(500003,"没有找到数据"),
    PARAM_ERROR(500004,"参数格式错误"),
    AUTH_ERROR(401,"没有权限,需要登录");

    //响应代码
    private Integer code;
    //响应消息
    private String message;

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    ResponseStatus(Integer status, String message) {
        this.code = status;
        this.message = message;
    }
}

3.5RsaUtils

package com.dmdd.javatestspringsecurity.util;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA工具类
 */
public class RsaUtils {

    public static final String RSA_SECRET = "blbweb@#$%"; //秘钥
    public static final String RSA_PATH = System.getProperty("user.dir")+"/rsa/";//秘钥保存位置
    public static final String RSA_PUB_KEY_PATH = RSA_PATH + "pubKey.rsa";//公钥路径
    public static final String RSA_PRI_KEY_PATH = RSA_PATH + "priKey.rsa";//私钥路径

    public static PublicKey publicKey; //公钥
    public static PrivateKey privateKey; //私钥

    /**
     * 类加载后,生成公钥和私钥文件
     */
    static {
        try {
            File rsa = new File(RSA_PATH);
            if (!rsa.exists()) {
                rsa.mkdirs();
            }
            File pubKey = new File(RSA_PUB_KEY_PATH);
            File priKey = new File(RSA_PRI_KEY_PATH);
            //判断公钥和私钥如果不存在就创建
            if (!priKey.exists() || !pubKey.exists()) {
                //创建公钥和私钥文件
                RsaUtils.generateKey(RSA_PUB_KEY_PATH, RSA_PRI_KEY_PATH, RSA_SECRET);
            }
            //读取公钥和私钥内容
            publicKey = RsaUtils.getPublicKey(RSA_PUB_KEY_PATH);
            privateKey = RsaUtils.getPrivateKey(RSA_PRI_KEY_PATH);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
    }

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

四.UserDetailServiceImpl

package com.dmdd.javatestspringsecurity.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dmdd.javatestspringsecurity.entity.User;
import com.dmdd.javatestspringsecurity.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.List;

/**
 * 实现自定义用户登录逻辑
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //按用户名查询用户信息
        User user = userService.getOne(new QueryWrapper().lambda().eq(User::getUsername, s));
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        //查询所有用户权限 List --> xx,xxx,xxx,xx --> List
        List authList = userService.getAuthoritiesByUsername(s);
        String auths = String.join(",", authList);
        //把用户信息包装到UserDetails的实现类User中
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                AuthorityUtils.commaSeparatedStringToAuthorityList(auths));
    }
}

1.这个类需要实现UserDetailService接口

2.重写loadUserBYUsername方法

3.将用户名,密码,授权信息包装到UserDetails的实现类User中。

五.成功处理器

package com.dmdd.javatestspringsecurity.config;

import com.dmdd.javatestspringsecurity.entity.vo.UserTokenVO;
import com.dmdd.javatestspringsecurity.util.JwtUtils;
import com.dmdd.javatestspringsecurity.util.ResponseResult;
import com.dmdd.javatestspringsecurity.util.RsaUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录成功处理器
 */
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    //登录成功的回调
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //获得用户名
        User user = (User) authentication.getPrincipal();
        //生成token字符串
        String token = JwtUtils.generateToken(user.getUsername(), RsaUtils.privateKey, JwtUtils.EXPIRE_MINUTES);
        log.info("生成token:{}",token);
        //发送token给前端
        ResponseResult.write(httpServletResponse,ResponseResult.ok(new UserTokenVO(user.getUsername(),token)));
    }
}

 因为项目前后端分离,服务器内不再拥有前端代码。所以不能直接用url的形式进行登陆成功后的跳转。因此采用成功处理器将用户名,秘钥,以及存活时间包装成token响应给前端

 六.SecurityConfig

package com.dmdd.javatestspringsecurity.config;

import com.dmdd.javatestspringsecurity.filter.RequestAuthenticationFilter;
import com.dmdd.javatestspringsecurity.util.ResponseResult;
import com.dmdd.javatestspringsecurity.util.ResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

/**
 * SpringSecurity的核心配置
 */
//启动权限控制的注解
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
//启动Security的验证
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //提供密码编码器
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    //配置验证用户的账号和密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //数据库用户验证
        auth.userDetailsService(userDetailsService);
    }

    //配置访问控制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //给请求授权
        http.authorizeRequests()
                //给登录相关的请求放行
                .antMatchers("/login","/logout").permitAll()
                //访问控制
                //其余的都拦截
                .anyRequest().authenticated()
                .and()
                //配置自定义登录
                .formLogin()
                .successHandler(loginSuccessHandler)//成功处理器
                .failureHandler(((httpServletRequest, httpServletResponse, e) -> { //登录失败处理器
                    ResponseResult.write(httpServletResponse,ResponseResult.error(ResponseStatus.LOGIN_ERROR));
                }))
                .and()
                .logout() //配置注销
                .logoutSuccessHandler(((httpServletRequest, httpServletResponse, authentication) -> { //注销成功
                    ResponseResult.write(httpServletResponse,ResponseResult.ok(ResponseStatus.OK));
                }))
                .clearAuthentication(true) //清除验证信息
                .and()
                .cors() //配置跨域
                .configurationSource(corsConfigurationSource())
                .and()
                .csrf().disable() //停止csrf
                .sessionManagement() //session管理
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //无状态,不使用session
                .and()
                .addFilter(new RequestAuthenticationFilter(authenticationManager())) //添加自定义验证过滤器
        ;
    }

    /**
     * 跨域配置对象
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        //配置允许访问的服务器域名
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

 可以直接拿来用。

七. filter

package com.dmdd.javatestspringsecurity.filter;

import com.dmdd.javatestspringsecurity.service.IUserService;
import com.dmdd.javatestspringsecurity.util.ApplicationContextUtils;
import com.dmdd.javatestspringsecurity.util.JwtUtils;
import com.dmdd.javatestspringsecurity.util.RsaUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

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.List;

/**
 * 请求验证过滤器
 */
@Slf4j
public class RequestAuthenticationFilter extends BasicAuthenticationFilter {

    public static final String AUTH_HEADER = "Authorization";

    //通过工具类获得service对象
    private IUserService userService = ApplicationContextUtils.getBean(IUserService.class);

    public RequestAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    //请求的过滤
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //从请求头获得token
        String token = request.getHeader(AUTH_HEADER);
        if(StringUtils.isEmpty(token)){
            //从请求参数获得token
            token = request.getParameter(AUTH_HEADER);
        }
        //如果读取不到,就拦截
        if(StringUtils.isEmpty(token)){
            log.info("读取不到token,请求{}被拦截",request.getRequestURL());
            chain.doFilter(request,response);
            return;
        }
        try {
            //对token进行解析
            String username = JwtUtils.getUsernameFromToken(token, RsaUtils.publicKey);
            //将用户的权限查询出来
            List authList = userService.getAuthoritiesByUsername(username);
            List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authList));
            //创建通行证
            UsernamePasswordAuthenticationToken authToken = new
                    UsernamePasswordAuthenticationToken(username,"",authorities);
            //把通行证交给Security
            SecurityContextHolder.getContext().setAuthentication(authToken);
        }catch (Exception ex){
            log.error("解析token失败",ex);
        }
        chain.doFilter(request,response);
    }
}

主要作用就是解析前端发过来的token,验证是否通过登陆,防止异常数据攻击服务器

注意:查询用户权限的方法需要自己编写

通过用户名查询该用户有哪些权限。然后交给服务器设置哪些接口需要哪些权限可以使用

八.项目结构

spring security 前后端分离 进行用户验证 权限登陆的实现代码(看不懂??直接cv)_第2张图片

你可能感兴趣的:(spring,mybatis,java)