流程:注册---加密密码---保存数据库---登录---授权---认证---效验数据库账号密码---生成token存redis---返回前端
第一步子模块引入依赖;版本号由父统一管理,这里有不理解的可以看我maven篇巩固一下。
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ran.youling</groupId>
<artifactId>maven-object-ran</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-security</artifactId>
<!--引入依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
</dependencies>
</project>
JwtUtils类,用于生成和解析token
package ran.youling.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类
*/
@Component
public class JwtUtils {
/**
* 签名密钥
*/
private static final String signKey = "shaniuguilai";
/**
* 有效时间
*/
private static final Long expire = 43200000L; //12小时过期
/**
* 生成JWT令牌
*
* @param claims JWT第二部分负载 payload 中存储的内容
*/
public static String generateJwt(Map<String, Object> claims) {
return Jwts.builder()
.addClaims(claims)//自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
.compact();
}
/**
* 解析JWT令牌
*
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt) {
return Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
}
// public static void main(String[] args) {
// Map<String, Object> map = new HashMap<>();
// map.put("username", "zhangsan");
// map.put("password", "123");
// String jwt_token = JwtUtils.generateJwt(map);
// System.out.println(jwt_token);
//
// Claims claims = JwtUtils.parseJWT(jwt_token);
// System.out.println(claims);
// }
}
Result<T>类 用于响应结果集
package ran.youling.util;
import lombok.Data;
@Data
public class Result<T> {
// 状态码。
private Integer code;
// 信息。
private String message;
// 数据。
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Result() {
}
public static <T> Result<T> build(Integer code, String message, T resultData) {
Result<T> result = new Result<>();
if (resultData != null) {
result.setData(resultData);
}
result.setCode(code);
result.setMessage(message);
return result;
}
public static Result success() {
Result result = new Result();
result.setCode(ResultCodeEnum.SUCCESS.getResultCode());
result.setMessage(ResultCodeEnum.SUCCESS.getResultMsg());
return result;
}
public static <T> Result<T> success(T resultData) {
return build(ResultCodeEnum.SUCCESS.getResultCode(), ResultCodeEnum.SUCCESS.getResultMsg(), resultData);
}
public static Result fail() {
Result result = new Result();
result.setCode(ResultCodeEnum.FAIL.getResultCode());
result.setMessage(ResultCodeEnum.FAIL.getResultMsg());
return result;
}
//失败的方法
public static <T> Result<T> fail(T resultData) {
return build(ResultCodeEnum.FAIL.getResultCode(), ResultCodeEnum.FAIL.getResultMsg(), resultData);
}
}
ResultCodeEnum枚举类,用于Result<T>类必要参数
package ran.youling.util;
import lombok.Getter;
@Getter// Lombok 插件注解。
public enum ResultCodeEnum {
// 自定义结果编码和结果信息。
SUCCESS(200,"成功"),
FAIL(400,"失败"),
SERVICE_ERROR(401, "服务异常"),
DATA_ERROR(402, "数据异常"),
ILLEGAL_REQUEST(403, "非法请求"),
REPEAT_SUBMIT(404, "重复提交"),
LOGIN_AUTH(405, "未登陆"),
PERMISSION(406, "没有权限"),
// 自定义...
;
// 结果编码。
private Integer resultCode;
// 结果信息。
private String resultMsg;
public Integer getResultCode() {
return resultCode;
}
public String getResultMsg() {
return resultMsg;
}
ResultCodeEnum(Integer resultCode, String resultMsg) {
this.resultCode = resultCode;
this.resultMsg = resultMsg;
}
}
建表
yml配置,nacos不是必须的(可选)
server:
port: 9005
spring:
application:
name: spring-security
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos服务器的地址
datasource: # 数据库配置应该放在这里
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/spring?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&autoReconnectForPools=true
username: root
password: 666666
#mybatisplus
mybatis-plus:
mapper-locations: classpath*:/mapper/*.xml # mapper映射文件
lobal-config:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启日至
RedisConfig配置类
package ran.youling.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 设置序列化方式
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
创建UserController进行测试
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/index")
public String index() {
return "欢迎访问首页";
}
浏览器输入 http://localhost:9005/user/login 需进行认证
分析:有些接口我们需要放行,比如登录接口、注册接口、首页等,否则前端页面无法访问
创建SecurityConfig配置类
package ran.youling.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/***
* NAME:Ran
* TIME:2025/1/2
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启安全认证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 数据库密码加密解密的bean
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 重写权限认证的方法
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 访问限制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//关闭csrf攻击
.authorizeHttpRequests().antMatchers("/login", "/enroll", "/index")//放行接口
.authenticated();//其它接口需验证
}
}
完善UserController
package ran.youling.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import ran.youling.pojo.User;
import ran.youling.service.impl.UserService;
import ran.youling.util.Result;
import java.util.Map;
/***
* NAME:Ran
* TIME:2025/1/12
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/index")
public String index() {
return "欢迎访问首页";
}
// 用户注册的方法
@PostMapping("/enroll")
public String userEnroll(@RequestBody User user) {
return userService.enroll(user);
}
// 模拟用户登录
@PostMapping("/login")
public Result<Map<String, String>> login(@RequestBody User user) {
return userService.login(user);
}
}
UserService接口
package ran.youling.service.impl;
import com.baomidou.mybatisplus.extension.service.IService;
import ran.youling.pojo.User;
import ran.youling.util.Result;
import java.util.Map;
/**
* @author Administrator
* @description 针对表【user】的数据库操作Service
* @createDate 2025-01-12 09:43:59
*/
public interface UserService extends IService<User> {
// 处理用户登录接口
Result<Map<String,String>> login(User user);
String enroll(User user);
}
UserServiceImpl类,这是权限认证核心代码,注释已标清
package ran.youling.service.impl.impl;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.checkerframework.checker.units.qual.A;
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.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import ran.youling.mapper.UserMapper;
import ran.youling.pojo.User;
import ran.youling.service.impl.UserService;
import ran.youling.util.JwtUtils;
import ran.youling.util.Result;
import ran.youling.vo.LoginUser;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author Administrator
* @description 针对表【user】的数据库操作Service实现
* @createDate 2025-01-12 09:43:59
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
// 注入认证过滤器
@Autowired
private AuthenticationManager authenticationManager;
// redis
@Autowired
private StringRedisTemplate redisTemplate;
//用于密码加密
@Autowired
private PasswordEncoder passwordEncoder;
//登录
@Override
public Result<Map<String, String>> login(User user) {
//把username和password等方法传递给security
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
// 注入认证过滤器 开始认证
Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
// 输出结果: UsernamePasswordAuthenticationToken [Principal=LoginUser(user=User(id=1, username=mengshujun, password=$2a$10$GysmSivtPRPPdT83CIgEGuusDd5soUIP275MdNjUmpuq.thg/YWxm, enabled=1)), Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]
if (Objects.isNull(authentication)) {
throw new UsernameNotFoundException("账号密码有误");
}
// 使用构造传递给LoginUser实体类
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String userId = loginUser.getUser().getId().toString();//用于redisKey的唯一标识
// 封装token
JSONObject from = JSONObject.from(loginUser);
String token = JwtUtils.generateJwt(from);
//保存到redis并设置过期时间
redisTemplate.opsForValue().set("login:" + userId, token, 2, TimeUnit.HOURS);//2,小时过期
HashMap<String, String> stringHashMap = new HashMap<>();
stringHashMap.put("token", token);
return Result.success(stringHashMap);
}
//注册
@Override
public String enroll(User user) {
// 调用passwordEncoder.encode进行密码加密,框架默认验证加密密码。
String encode = passwordEncoder.encode(user.getPassword());
User userOverride = new User();
userOverride.setUsername(user.getUsername());
userOverride.setPassword(encode);
baseMapper.insert(userOverride);
System.out.println("密码加密后:" + encode);
return encode;
}
}
我们需要LoginUser实体类(不是sql的实体类)实现UserDetails接口重写接口所有方法,用来接收security返回的数据
package ran.youling.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import ran.youling.pojo.User;
import java.util.Collection;
/***
* NAME:Ran
* TIME:2025/1/12
* 保存登录成功的数据
*/
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
public LoginUser(User user) {
this.user = user;
}
// 权限认证集合
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.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;
}
}
创建UserDetailsService类 用来访问数据库
package ran.youling.service.impl.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import ran.youling.pojo.User;
import ran.youling.service.impl.UserService;
import ran.youling.vo.LoginUser;
import javax.annotation.Resource;
import java.util.Objects;
/***
* NAME:Ran
* TIME:2025/1/12
*/
@Service
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
/**
* 实现UserDetailsService重写loadUserByUsername方法 执行数据库操作
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Resource
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username", username);
User user = userService.getOne(userQueryWrapper);
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("暂无该用户");
}
return new LoginUser(user);
}
}
测试
标签:return,ran,springframework,springSecurity,开箱,效验,org,import,public From: https://blog.csdn.net/m0_74759856/article/details/145185157