阅读前请先下载项目源码,边读边看源码以加深理解和实操,
源码地址已放于文章末尾!
效果预览:





04-安全认证-Aunt-I-don’t-want-to-work-hard-anymore!
各位小伙伴,前方高能预警!今天我们要聊的话题,是后端开发中“皇冠上的明珠”——安全认证。如果说之前的CRUD是“盖房子”,那今天我们做的就是给房子装上“防盗门”和“监控系统”。没有它,你的系统就等于是在“裸奔”,任何人都可以进来“为所欲为”。
在上一章的结尾,我留了一个巨大的“坑”:我们的用户信息,特别是密码,是明文存在数据库里的!这要是被“脱裤”,那项目负责人就得卷铺盖走人了。而且,我们的API接口也没有任何保护,谁都可以调用。这可不行!
所以,今天咱们要解决两个核心问题:
- 密码加密:绝不能让密码明文出现在数据库里。
- 接口认证:用户登录后才能访问受保护的资源,就像你得刷门禁卡才能进小区一样。
为了解决这两个问题,我们要请出两位“大神”:Spring Security 和 JWT。
Spring Security + JWT:后端界的“黄金搭档”
- Spring Security: 这是Spring家族提供的专业“保安队”。它功能极其强大,能提供一整套完整的安全服务,包括认证(你是谁?)、授权(你能干啥?)、攻击防护等。但它有个毛病,就是配置起来有点复杂,像个固执的老头。
- JWT (JSON Web Token): 这是一种轻量级的认证规范。用户登录成功后,服务器会生成一个加密的“令牌”(Token),像一张“限时通行证”,发给客户端。之后客户端每次请求需要认证的接口时,都必须带上这张“通行证”。服务器看到通行证合法,就放行。
为啥要把它俩结合起来?
Spring Security默认的认证方式是基于Session的,这在前后端分离的项目里不太好使。而JWT是无状态的,天生就适合RESTful API。所以我们用Spring Security的强大框架,但把它的核心认证逻辑换成JWT的“令牌”模式,取长补短,完美!
这个过程就像阿姨不想努力了…啊不,是《让子弹飞》里的场景:
第一步:添加新的“兵器”
老规矩,先在 pom.xml 里加入我们需要的依赖:
<!-- ... other dependencies ... -->
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT (Java JWT) -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.3</version> <!-- 使用一个较新的稳定版本 -->
</dependency>
加完之后,记得刷新一下你的Maven项目,让它把新的“兵器”下载回来。
第二步:编写JWT工具类
我们需要一个工具类来专门负责生成和校验JWT。在 com.weke.learningsystem 包下创建一个新的包 utils,然后在里面创建 JwtUtil.java。
package com.weke.learningsystem.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key"; // 密钥,生产环境一定要更复杂
private static final long EXPIRATION_TIME = 864_000_00; // 24小时
// 生成Token
public static String generateToken(String username) {
return JWT.create()
.withSubject(username)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(Algorithm.HMAC512(SECRET_KEY));
}
// 校验Token并获取用户名
public static String validateToken(String token) {
DecodedJWT jwt = JWT.require(Algorithm.HMAC512(SECRET_KEY))
.build()
.verify(token);
return jwt.getSubject();
}
}
注意: SECRET_KEY 是加密的密钥,非常重要,千万不要泄露!
第三步:改造用户服务(注册与登录)
现在,我们要对 UserService 进行“手术”,实现真正的注册和登录逻辑。
首先,修改 UserService 接口,增加两个新方法:
UserService.java:
// ... imports ...
public interface UserService extends IService<User> {
User register(User user);
String login(String username, String password);
}
然后,在 UserServiceImpl 中实现这两个方法:
UserServiceImpl.java:
// ... imports ...
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public User register(User user) {
// 使用BCrypt对密码进行加密
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
this.save(user); // this.save()是ServiceImpl自带的方法
return user;
}
@Override
public String login(String username, String password) {
User user = this.query().eq("username", username).one();
if (user != null && passwordEncoder.matches(password, user.getPassword())) {
// 密码匹配,生成JWT
return JwtUtil.generateToken(username);
}
// 登录失败
return null;
}
}
代码解读:
BCryptPasswordEncoder: 这是Spring Security提供的密码加密工具。我们不能直接把它new出来,而是要把它配置成一个Bean,让Spring来管理。passwordEncoder.encode(): 将用户传来的原始密码,加密成一长串谁也看不懂的字符串。passwordEncoder.matches(): 用于比对用户输入的密码和数据库中加密过的密码是否匹配。
配置密码加密器Bean:
为了让 BCryptPasswordEncoder 能被 @Autowired,我们需要在主启动类 LearningSystemApplication 的同级或者上级创建一个 config 包,并在里面创建一个 SecurityConfig 类。
config/SecurityConfig.java
package com.weke.learningsystem.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
第四步:改造Controller
有了新的业务逻辑,我们的 UserController 也要与时俱进。
// ... imports ...
import java.util.Map;
@RestController
@RequestMapping("/auth") // 我们把路径改成/auth,更符合语义
public class AuthController { // 类名也改一下
@Autowired
private UserService userService;
// 注册
@PostMapping("/register")
public User register(@RequestBody User user) {
return userService.register(user);
}
// 登录
@PostMapping("/login")
public Map<String, String> login(@RequestBody Map<String, String> credentials) {
String username = credentials.get("username");
String password = credentials.get("password");
String token = userService.login(username, password);
if (token != null) {
return Map.of("token", token);
}
// 在实际项目中,应该返回更详细的错误信息
throw new RuntimeException("Invalid credentials");
}
}
我们把原来的 UserController 改成了 AuthController,专门负责认证。原来的查询用户接口暂时可以注释掉,因为它们还没有受到保护。
第五步:配置Spring Security
这是最硬核的一步,我们要告诉Spring Security这个“老头”按我们的规矩来办事:放行登录和注册接口,其他的接口需要带“令牌”才能访问。
修改我们刚才创建的 SecurityConfig.java:
// ... imports ...
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;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ... BCryptPasswordEncoder的Bean ...
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable() // 关闭csrf
.authorizeRequests()
.antMatchers("/auth/**").permitAll() // 放行/auth/下的所有请求
.anyRequest().authenticated() // 其他所有请求都需要认证
.and()
// 我们不再需要session了,所以把它关了
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 这里我们后面还要加上JWT的过滤器
}
}
注意:WebSecurityConfigurerAdapter 在新版的Spring Security中已被弃用,但为了方便理解,我们暂时先用着。新版的配置方式我们后续可以再升级。
现在,重启你的应用。你会发现,如果你直接访问 http://localhost:8080/users(假如你没删掉这个接口),会直接返回401或403错误,说明我们的“防盗门”已经生效了!
而你访问 /auth/register 和 /auth/login 则是正常的。你可以试试注册一个新用户,然后用新用户登录,看看是否能成功拿到一长串的JWT Token。
下一期预告:
我们已经成功生成了Token,但还没用上它!服务器还不知道怎么去校验客户端传来的Token。下一章,我们将编写一个JWT认证过滤器,把它加入到Spring Security的“安保流程”中,真正完成认证闭环。同时,我们也要开始捣鼓一下“尘封已久”的前端代码,让它跟我们的新后端“对上暗号”!
源码下载地址:
https://download.youkuaiyun.com/download/THMAIL/91753658

1064

被折叠的 条评论
为什么被折叠?



