SpringSecurity认证
创始人
2024-05-26 06:29:12
0

文章目录

  • 登陆校验流程
  • 依赖
  • yaml
  • 实现
    • 建表、工具类、实体类
    • 加密器、AuthenticationManager
    • 登录逻辑
    • 登录过滤器、配置过滤器
    • 登出

登陆校验流程

认证
在这里插入图片描述
在这里插入图片描述
登录:
​ ①自定义登录接口
​ 调用ProviderManager的方法进行认证
如果认证通过生成token,根据userId把用户信息存入redis中,返回token给前端
​ ②自定义UserDetailsService
​ 在这个实现类中去查询数据库,封装为UserDetails对象返回

校验:
​ ①定义Jwt认证过滤器
​ 获取token
​ 解析token获取其中的userid
​ 从redis中获取用户信息
​ 存入SecurityContextHolder

依赖

com.baomidoumybatis-plus-boot-starter3.4.3

mysqlmysql-connector-java

org.springframework.bootspring-boot-starter-data-redis


com.alibabafastjson1.2.33


io.jsonwebtokenjjwt0.9.0

org.projectlomboklombok

org.springframework.bootspring-boot-starter-security

yaml

spring:datasource:url: jdbc:mysql://192.168.111.101:3306/security?characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver

实现

建表、工具类、实体类

建表语句


CREATE TABLE `user` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',`nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',`password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',`status` CHAR(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',`email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱',`phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手机号',`sex` CHAR(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',`avatar` VARCHAR(128) DEFAULT NULL COMMENT '头像',`user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',`create_by` BIGINT(20) DEFAULT NULL COMMENT '创建人的用户id',`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',`update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人',`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',`del_flag` INT(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

JWT工具类


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 = "xd52s8w";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();}public static void main(String[] args) throws Exception {String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";Claims claims = parseJWT(token);System.out.println(claims);}/*** 生成加密后的秘钥 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;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}

实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;/*** 用户表(User)实体类*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/private Long id;/*** 用户名*/private String userName;/*** 昵称*/private String nickName;/*** 密码*/private String password;/*** 账号状态(0正常 1停用)*/private String status;/*** 邮箱*/private String email;/*** 手机号*/private String phonenumber;/*** 用户性别(0男,1女,2未知)*/private String sex;/*** 头像*/private String avatar;/*** 用户类型(0管理员,1普通用户)*/private String userType;/*** 创建人的用户id*/private Long createBy;/*** 创建时间*/private Date createTime;/*** 更新人*/private Long updateBy;/*** 更新时间*/private Date updateTime;/*** 删除标志(0代表未删除,1代表已删除)*/private Integer delFlag;
}

UserDetails: loadUserByUsername 方法返回的数据

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;@Overridepublic Collection getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassword ();}@Overridepublic String getUsername() {return user.getUserName ();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

UserDetailsService ,通过loadUserByUsername获取用户,封装成UserDetails 对象返回

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.security.mapper.UserMapper;
import com.example.security.vo.LoginUser;
import com.example.security.vo.User;
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;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询用户信息LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<> ();queryWrapper.eq (User::getUserName, username);User user = userMapper.selectOne (queryWrapper);if (user == null) {// 用户不存在throw new RuntimeException ("用户不存在");}//TODO 根据用户查询权限信息 添加到LoginUser中return new LoginUser (user);}
}

加密器、AuthenticationManager

密码加密存储、登陆接口

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.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;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 密码加密存储** @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder ();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf ().disable ()//不通过Session获取SecurityContext.sessionManagement ().sessionCreationPolicy (SessionCreationPolicy.STATELESS).and ().authorizeRequests ()// 对于登录接口 允许匿名访问.antMatchers ("/user/login").anonymous ()// 除上面外的所有请求 全部需要鉴权认证.anyRequest ().authenticated ();}/*** 登陆接口* 通过AuthenticationManager的authenticate方法来进行用户认证* 所以需要在SecurityConfig中配置把AuthenticationManager注入容器** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean ();}
}

登录逻辑

LoginServiceImpl 登录逻辑

import com.alibaba.fastjson.JSONObject;
import com.example.security.config.JwtUtil;
import com.example.security.vo.LoginUser;
import com.example.security.vo.ResponseResult;
import com.example.security.vo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Objects;@Service
public class LoginServiceImpl implements LoginServcie {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate StringRedisTemplate redisTemplate;@Overridepublic ResponseResult login(User user) {UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken (user.getUserName (), user.getPassword ());Authentication authenticate = authenticationManager.authenticate (authenticationToken);if (Objects.isNull (authenticate)) {throw new RuntimeException ("用户名或密码错误");}// 使用userId生成tokenLoginUser loginUser = (LoginUser) authenticate.getPrincipal ();String userId = loginUser.getUser ().getId ().toString ();String token = JwtUtil.createJWT (userId);// authenticate存入redisredisTemplate.opsForValue ().set ("login:" + userId, JSONObject.toJSONString (loginUser));// 把token响应给前端HashMap map = new HashMap<> ();map.put ("token", token);return new ResponseResult (200, "登陆成功", map);}
}

登录过滤器、配置过滤器

对需要登录的接口进行过滤,从redis中查询用户信息,未登录就 不放行

import com.alibaba.fastjson.JSONObject;
import com.example.security.vo.LoginUser;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
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;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 对于需要登录的接口进行拦截* 看看用户信息是否存在** @param request* @param response* @param filterChain* @throws ServletException* @throws IOException*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 获取 tokenString token = request.getHeader ("token");if (!StringUtils.hasText (token)) {// 不携带token,放行filterChain.doFilter (request, response);return;}//解析tokenString userId;try {Claims claims = JwtUtil.parseJWT (token);userId = claims.getSubject ();} catch (Exception e) {e.printStackTrace ();throw new RuntimeException ("token非法");}//从redis中获取用户信息String redisKey = "login:" + userId;LoginUser loginUser = JSONObject.parseObject (redisTemplate.opsForValue ().get (redisKey), LoginUser.class);if (Objects.isNull (loginUser)) {// 没有token,就是未登录throw new RuntimeException ("用户未登录");}//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken (loginUser, null, null);SecurityContextHolder.getContext ().setAuthentication (authenticationToken);// 放行filterChain.doFilter (request, response);}
}

配置过滤器,使其生效

   @Autowiredprivate JwtAuthenticationTokenFilter filter;@Overrideprotected void configure(HttpSecurity http) throws Exception {// 把token校验过滤器 添加到过滤器链中http.addFilterBefore (filter, UsernamePasswordAuthenticationFilter.class);http//关闭csrf.csrf ().disable ()//不通过Session获取SecurityContext.sessionManagement ().sessionCreationPolicy (SessionCreationPolicy.STATELESS).and ().authorizeRequests ()// 对于登录接口 允许匿名访问.antMatchers ("/user/login").anonymous ()// 除上面外的所有请求 全部需要鉴权认证.anyRequest ().authenticated ();}

登出

将用户信息从Redis中删除
登出


public ResponseResult logout() {Authentication authentication = SecurityContextHolder.getContext ().getAuthentication ();LoginUser loginUser = (LoginUser) authentication.getPrincipal ();Long userid = loginUser.getUser ().getId ();redisTemplate.delete ("login:" + userid);return new ResponseResult (200, "退出成功");
}

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...