文章目录
-
-
- 项目业务层
- 一、登录实现
-
- [1.1 令牌技术](#1.1 令牌技术)
- [1.2 JWT 令牌](#1.2 JWT 令牌)
- [1.3 JWT令牌使用](#1.3 JWT令牌使用)
- 二、强制登录(拦截器)
- 三、显示用户信息
- 四、用户登出
-

项目业务层
一、登录实现
1.1 令牌技术
令牌技术:
使用令牌技术,考虑下述场景时:
- 用户登录:用户登录请求,经过负载均衡,把请求转给了第一台服务器,第一台服务器进行账号密码验证,验证成功后,生成一个令牌,并返回给客户端。
- 客户端收到令牌之后,把令牌存储起来。可以存储在Cookie中,也可以存储在其他的存储空间(比如localStorage)。
- 查询操作:用户登录成功之后,携带令牌继续执行查询操作,比如查询博客列表。此时请求转发到了第二台机器,第二台机器会先进行权限验证操作。服务器验证令牌是否有效,如果有效,说明用户已经执行了登录操作,如果令牌是无效的,就说明用户之前未执行登录操作。

令牌优缺点:
优点:
- 解决了集群环境下的认证问题
- 减轻了服务器的存储压力(不用存储在服务器中)
缺点:需要自己实现(包括令牌生成,传递,校验)
1.2 JWT 令牌
介绍:JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),用于客户端和服务器之间传递安全可靠的信息。官方网址:https://www.jwt.io/
其本质是一个token,是一种紧凑的URL安全方法。
JWT组成
JWT由三部分组成,每部分中间使用点(.)分隔,比如:aaaaa.bbbbb.ccccc
- Header(头部):头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)。
- Payload(负载) :负载部分是存放有效信息的地方,里面是一些自定义内容,比如:
{"userId":"123","userName":"zhangsan"},也可以存在JWT提供的现成字段,比如exp(过期时间戳)等。 - Signature(签名):此部分用于防止JWT内容被篡改,确保安全性。
Signature 防止被篡改,而不是防止被解析。
JWT之所以安全,就是因为最后的签名。JWT当中任何一个字符被篡改,整个令牌都会校验失败。
就好比我们的身份证,之所以能标识一个人的身份,是因为它不能被篡改,而不是因为内容加密(任何人都可以看到身份证的信息,JWT也是)。
1.3 JWT令牌使用
用户登录按照如下功能进行:
- 登录页面把用户名密码提交给服务器。
- 服务器端验证用户名密码是否正确,如果正确,服务器生成令牌,下发给客户端。
- 客户端把令牌存储起来(比如Cookie、local storage等),后续请求时,把token发给服务器。
- 服务器对令牌进行校验,如果令牌正确,进行下一步操作。

- 引入JWT令牌依赖
xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
- 约定前后端交互接口
接口信息
- 请求路径 :
/user/login - 请求方式:POST
请求参数
json
{
"userName": "test",
"password": "123456"
}
响应示例
json
{
"code": 200,
"errMsg": null,
"data": {
"userId": 1,
"token": "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6MSwiaWF0IjoxNzIxMTE5NjgyLCJleHAiOjE3MjE1MjE0ODJ9.5hwKlAh2jPPBNn3uPja4JTGguZNB3QrpRoPqCep7qME"
}
}
说明
- 验证成功:返回
code=200及包含userId和token的响应数据。 - 验证失败:返回空字符串。
创建JWT工具类:
java
public class JwtUtils {
private static final long EXPIRATION_TIME = 7*24*60*60*1000;
private static final String secretString = "kIx9zHQe56aRa3D4THlPuj+ruT1yIhTAdgxRz8VLrdA=";
private static final Key key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
/**
* 生成令牌
*/
public static String genJwt(Map<String,Object> map){
return Jwts.builder()
.setClaims(map)
.signWith(key, SignatureAlgorithm.HS256)
.setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_TIME))
.compact();
}
/**
* 校验令牌
*/
public static Boolean check(String token){
if(!StringUtils.hasLength(token)){
return false;
}
JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
try {
Claims body = build.parseClaimsJws(token).getBody();
return true;
}catch (Exception e){
return false;
}
}
}
- 实现Controller 层
java
@PostMapping("/login")
public UserLoginResponse login(@Validated @RequestBody UserLoginRequest loginRequest){
log.info("用户登录, username:{}",loginRequest.getUserName());
// 参数校验
return userService.check(loginRequest);
}
- 实现Service 层
java
@Override
public UserLoginResponse check(UserLoginRequest loginRequest) {
// 判断密码是否正确
UserInfo userInfo = selectUserInfo(loginRequest.getUserName());
if(userInfo==null) {
throw new BlogException("用户不存在");
}
if(!loginRequest.getPassword().equals(userInfo.getPassword())){
throw new BlogException("密码不正确");
}
Map<String,Object> map = new HashMap<>();
map.put("id",userInfo.getId());
map.put("name",userInfo.getUserName());
String token = JwtUtils.genJwt(map);
return new UserLoginResponse(userInfo.getId(),token);
}
测试后端接口返回正确,token已返回:

二、强制登录(拦截器)
当用户访问博客列表和详情页等时,如果用户尚未登录,就自动跳转到登录页面。使用拦截器来完成,从前端header中获取token,然后进行校验token是否合法。如果合法则正常访问,如果不合法则强制回到登录页。
添加拦截器:
java
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取token
String userToken = request.getHeader(Constants.HEADER_USER_TOKEN_KEY);
log.info("从header中获取到token,url:{},token:{}",request.getRequestURI(),userToken);
// 校验token
Boolean check = JwtUtils.check(userToken);
if(check){
return true;
}
log.error("token检验失败,token:{}",userToken);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
}
代码中HEADER_USER_TOKEN_KEY静态变量需要和前端进行对应相等。
此外,添加Config类实现
WebMvcConfigurer来排掉不需要拦截的路径。
验证发现,当token为空或者不合法时,强制跳转到登录页。
三、显示用户信息
用户信息希望可以随着用户登录而改变,并且如果当前页面为博客列表页,则显示当前登录用户的信息。如果当前页面是博客详情页,则显示该博客的作者用户信息。
- 约定前后端交互接口
获取登录用户信息:
- 请求路径 :
/user/getUserInfo - 请求方式:GET
- 请求参数 :
userId=1
响应示例
json
{
"id": 1,
"username": "test",
"githubUrl": "bite.gitee.com"
}
获取博客作者信息:
- 请求路径 :
/user/getAuthorInfo - 请求方式:GET
- 请求参数 :
blogId=1 - 说明:在博客详情页,获取当前文章作者的用户信息
响应示例
json
{
"id": 1,
"username": "test",
"githubUrl": "bite.gitee.com"
}
- 实现Controller 层:
java
@GetMapping("/getUserInfo")
public UserInfoResponse getUserInfo(@NotNull Integer userId){
log.info("获取用户信息,userId:{}",userId);
return userService.getUserInfo(userId);
}
@GetMapping("/getAuthorInfo")
public UserInfoResponse getAuthorInfo(@NotNull Integer blogId){
log.info("获取博客的作者信息,blogId:{}",blogId);
return userService.getAuthorInfo(blogId);
}
- 实现Service 层
java
@Override
public UserInfoResponse getUserInfo(Integer userId) {
return BeanTransUtils.transUserInfo(selectUserInfoById(userId));
}
@Override
public UserInfoResponse getAuthorInfo(Integer blogId) {
BlogInfo blogInfo = blogService.getBlogInfo(blogId);
if(blogInfo == null){
throw new BlogException("博客不存在");
}
return BeanTransUtils.transUserInfo(selectUserInfoById(blogInfo.getUserId()));
}
service 层还需进行实体数据的转换,因为接口需要的实体类和数据库定义的实体类不一致。数据转换已单独封装到公共模块里,代码与上一篇博客中的示例类似。
验证接口发现正确返回对应的用户信息:

四、用户登出
用户登出操作只需将客户端保存的token删除掉即可, 并且返回到登录界面。
前端代码中添加了onclick事件,当点击时去除token即可。