springsecurity---使用流程、加密机制、自定义密码匹配器、token字符串生成

发布于:2025-07-05 ⋅ 阅读:(13) ⋅ 点赞:(0)

目录

权限控制

相关框架

SpringSecurity

springsecurity使用流程

1、搭建环境实现默认用户名和密码登录

2、使用数据库表中定义好的用户名和密码访问实现等值密码匹配

1)sql文件

2)搭建jdbc或者mybatis或者mybatis-plus环境

3)配置mybatis-plus环境

4)使用mybatisX 生成基本的service和mapper

5)创建一个类实现UserDetailsService接口 ,重写方法(根据用户名到数据库中查找用户对象)

6)创建一个LoginUser实现UserDetails接口,封装用户信息

3、加密机制 自定义密码匹配器

1)创建加密对象

2)使用测试类,生成临时密码,测试匹配方法

4、自定义登录接口

5、token字符串的生成


权限控制

一、认证:是否登录成功。

二、授权:权限控制,授予权限、校验权限。

相关框架

一、shiro

Shiro 是 apache权限框架,较之 JAAS 和 Spring Security,Shiro 在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。 Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证授权管理会话以及密码加密

二、springsecurity

Spring Security 是 Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰

富的功能,社区资源也比Shiro丰富。

一般来说中大型的项目都是使用SpringSecurity来做安全框架。

小项目用Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。

SpringSecurity

springsecurity 和 其他组件的简单交互

前后端分离场景下,登录校验流程的核心思路:token。 Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生 成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无 需再次带上用户名和密码。

使用token机制的身份验证方法,在服务器端不需要存储用户的登录记录。

JWT:JWT全面解读、详细使用步骤 - 简书

springsecurity使用流程

1、搭建环境实现默认用户名和密码登录

springboot环境+springweb+springsecurity权限jar包

访问目标方法时,自动跳转到/login接口,输入用户名user和默认密码,登录成功后,自动跳转到目标方法。

localhost:8080/login

2、使用数据库表中定义好的用户名和密码访问实现等值密码匹配

1)sql文件
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '用户名',
  `nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '昵称',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '密码',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
  `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `phonenumber` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
  `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
  `avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
  `user_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',
  `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人的用户id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `del_flag` int(11) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
​
 
INSERT INTO `sys_user` VALUES (1, '张三', '张三', '{noop}1234', '0', NULL, NULL, NULL, NULL, '1', NULL, NULL, NULL, NULL, 0);
2)搭建jdbc或者mybatis或者mybatis-plus环境
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>
3)配置mybatis-plus环境
server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springsecurity
    username: root
    password: 123456
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  type-aliases-package: com.hl.springsecurity01.domain
  mapper-locations: classpath:/mapper/*.xml
4)使用mybatisX 生成基本的service和mapper

 

5)创建一个类实现UserDetailsService接口 ,重写方法(根据用户名到数据库中查找用户对象)
package com.hl.springsecurity01.security;
​
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hl.springsecurity01.domain.SysUser;
import com.hl.springsecurity01.service.SysUserService;
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.List;
​
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private SysUserService sysUserService;
    /*
    根据用户名查找用户对象
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名,到数据库表中,查找用户对象
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("user_name", username);
        List<SysUser> list = sysUserService.list(queryWrapper);
        //判断用户是否存在
        LoginUser user = null;
        if(list != null && list.size() > 0){
            SysUser sysUser = list.get(0);
            //封装数据到UserDetails接口实现类对象中
            user = new LoginUser(sysUser);
        }
        return user;
    }
}
6)创建一个LoginUser实现UserDetails接口,封装用户信息
package com.hl.springsecurity01.security;
​
import com.hl.springsecurity01.domain.SysUser;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
​
import java.util.Collection;
import java.util.Collections;
/*
创建类实现UsersDetail接口,存储当前登录成功的用户信息
 */
@Data
public class LoginUser implements UserDetails {
​
    private SysUser sysUser;
​
    public LoginUser() {
    }
    public LoginUser(SysUser sysUser) {
        this.sysUser = sysUser;
    }
​
    //返回用户权限信息,返回权限列表
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.emptyList();
    }
​
    @Override
    public String getPassword() {
        return sysUser.getPassword();
    }
​
    @Override
    public String getUsername() {
        return sysUser.getUserName();
    }
​
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
​
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
​
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
​
    @Override
    public boolean isEnabled() {
        return true;
    }
}

3、加密机制 自定义密码匹配器

1)创建加密对象
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    /*
    创建加密对象(密码匹配器对象)
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
2)使用测试类,生成临时密码,测试匹配方法
@SpringBootTest
class Springsecurity01ApplicationTests {
    @Autowired
    private PasswordEncoder passwordEncoder;
​
    @Test
    void contextLoads() {
        String pwd = passwordEncoder.encode("1234");
        System.out.println(pwd);
        //$2a$10$DpNgEn0fiYStJJ/EoYxf7uhBOMnuqK/UdBKmexX5KrEVPLuGQ0LsS
        //$2a$10$sLUel1CyTVY1kDWM5BwlNu1WMLMqlys00NIir0SiEgR0A5ItM1Vda
        //比对密码
        boolean flag1 = passwordEncoder.matches("1234", "$2a$10$DpNgEn0fiYStJJ/EoYxf7uhBOMnuqK/UdBKmexX5KrEVPLuGQ0LsS");
        boolean flag2 = passwordEncoder.matches("1234", "$2a$10$sLUel1CyTVY1kDWM5BwlNu1WMLMqlys00NIir0SiEgR0A5ItM1Vda");
        System.out.println(flag1);
        System.out.println(flag2);
    }
​
}
$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
├─┬─┼──┬─┼──────────────┼───────────────────────────────────
  │ │  │  │      │                     
  │ │  │  │      └─ 哈希值(31字节)
  │ │  │  └─ 盐值(22字符,16字节)
  │ │  └─ cost参数(12 → 2^12=4096轮迭代)
  │ └─ 算法版本(2a)
  └─ 固定前缀
$<算法版本>$<cost>$<salt><hash>

4、自定义登录接口

@RestController
public class LoginController {
    @Autowired
    private LoginService loginService;
    @RequestMapping("/login")
    public R login(String username, String password) throws AuthenticationException {
        //调用service
        return loginService.login(username, password);
    }
}
public interface LoginService {
    public R login(String username, String password) throws AuthenticationException;
}
@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Override
    public R login(String username, String password) throws AuthenticationException {
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(username, password);
        //调用认证提供器的认证方法,进行用户名,密码认证
        Authentication authentication = authenticationManager.authenticate(token);
        //根据返回值判断是否认证成功
        if(authentication == null){
            //认证失败
            throw  new AuthenticationException("用户名或者密码错误");
        }
        if(authentication.isAuthenticated()){//认证成功
            //返回 code ,msg,token
            return R.ok("token.............","认证成功");
        }
        return null;
    }
}
package com.hl.springsecurity01.security;
​
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    /*
    创建加密对象(密码匹配器对象)
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
    }
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

 将数据库密码字段换成生成的加密字段$2a$10$DpNgEn0fiYStJJ/EoYxf7uhBOMnuqK/UdBKmexX5KrEVPLuGQ0LsS

登录成功,看到json返回,失败,没有任何提示。

5、token字符串的生成

<!--生成token-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

JwtUtil工具类

package com.hl.springsecurity01.util;
​
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
​
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
​
/**
 * JWT工具类
 */
public class JwtUtil {
    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "test";
​
    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }
​
    /**
     * 生成jtw字符串
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }
​
    /**
     * 生成jtw字符串
     * @param subject token中要存放的数据(json格式)
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }
​
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }
​
    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }
​
​
    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
​
    /**
     * 解析jwt
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
​
    /**
     *   用于测试
     */
    public static void main(String[] args) throws Exception {
//        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
//        Claims claims = parseJWT(token);
//        System.out.println(claims);
        String token = createJWT("1");
        String token1 = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJlM2UzMGE0ZjhhMGE0OTQ1ODNjMzZlZmNjMWQ3YzQ4YiIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTc1MTUzMjI0MiwiZXhwIjoxNzUxNTM1ODQyfQ.ALfU833lUe1bCRlsAwcDd_mwD5j3sCtFCO3Ue1E-WWw";
        System.out.println(token);
        Claims claims = parseJWT(token1);
        System.out.println(claims);
    }
​
}
package com.hl.springsecurity01.service.impl;
​
import com.hl.springsecurity01.domain.R;
import com.hl.springsecurity01.security.LoginUser;
import com.hl.springsecurity01.service.LoginService;
import com.hl.springsecurity01.util.JwtUtil;
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.stereotype.Service;
​
import javax.security.sasl.AuthenticationException;
​
@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Override
    public R login(String username, String password) throws AuthenticationException {
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(username, password);
        //调用认证提供器的认证方法,进行用户名,密码认证
        Authentication authentication = authenticationManager.authenticate(token);
        //根据返回值判断是否认证成功
        if(authentication == null){
            //认证失败
            throw  new AuthenticationException("用户名或者密码错误");
        }
        if(authentication.isAuthenticated()){//认证成功
            //获取用户身份 LoginUser
            LoginUser user = (LoginUser) authentication.getPrincipal();
            //获取用户id
            Long id = user.getSysUser().getId();
            //根据用户id,生成token
            String token2 = JwtUtil.createJWT(id+"");
            //返回 code ,msg,token
            return R.ok(token2,"认证成功");
        }
        return null;
    }
}