登陆认证
基于Session的认证流程
优点
- 用户友好:
• 用户登录后,无需在每个页面请求中重复输入用户名和密码。• 提供了一种无缝的用户体验,用户在浏览网站时不会频繁被要求重新认证。 - 安全性:
• 服务器端维护用户状态信息,而客户端仅存储一个Session ID,这样可以减少敏感信息的暴露。
• Session ID通常具有时效性,可以设置过期时间,增加了安全性。 - 易于管理:
• 服务器可以轻松地管理用户会话,例如,可以控制Session的生命周期,包括创建、更新和销毁Session。
• 可以对Session进行序列化,以便在服务器重启时恢复用户会话。 - 灵活性:
• 可以存储用户特定的信息,如用户角色、权限等,以便在用户会话期间使用。
• 可以根据需要自定义Session的行为,例如,可以设置Session的有效期、锁定机制等。 - 支持分布式部署:
• 在分布式系统中,可以通过共享Session存储或使用Session复制技术来支持Session的一致性。 - 防止CSRF攻击:
• 通过在Session中存储CSRF令牌,并在表单提交时验证令牌,可以有效地防止跨站请求伪造(CSRF)攻击。
基于Token的认证流程
优点
- 无状态和可扩展性:
- • 基于Token的认证是无状态的,服务器不需要存储Session信息,这使得系统更容易扩展,特别是在分布式系统中。
- 安全性:
• Token通常经过数字签名,这确保了Token在传输过程中未被篡改。
• 可以使用强大的加密算法来生成Token,增加了安全性。 - 支持跨域认证:
• 由于Token是自包含的,它可以在多个域之间安全地传递,这使得它非常适合单点登录(SSO)场景。 - 自定义性强:
• Token可以包含丰富的用户信息和权限数据,这些信息可以根据需要进行定制。 - 减少服务器负担:
• 由于服务器不需要存储Session信息,这减少了服务器的存储和内存负担。 - 支持移动和分布式设备:
• Token可以在多种环境中使用,包括移动设备和分布式系统中,这使得它非常适合现代的移动和云应用。 - 简单易于实现:
• 基于Token的认证流程相对简单,易于实现和维护。
Token详解 :
Token是一种令牌,它在计算机身份验证中用于代表用户身份或会话。在Web开发中,Token通常用于用户认证和授权,尤其是在无状态的API服务和单点登录(SSO)系统中。以下是对Token的详细解释:
Token的组成
以JWT(JSON Web Tokens)为例,一个Token通常由三部分组成,用点(.)分隔:
- Header(头部):
• 描述Token的元数据,例如Token的类型(JWT)和使用的签名算法(如HMAC SHA256或RSA)。
- Payload(负载):
• 包含声明(Claims),即有关实体(通常是用户)和其他数据的声明。
• 可以包含用户的角色、权限、Token的发行者、过期时间等信息。
- Signature(签名):
• 用于验证Token在传输过程中未被篡改。
• 通过使用头部指定的算法和密钥对头部和负载进行签名生成。
Token的安全性
数字签名:
• 使用私钥对Token进行签名,确保Token的完整性和真实性。
加密:
• 使用对称或非对称加密算法对Token进行加密,确保只有授权的接收者才能解密和读取Token内容。
过期时间:
• 设置Token的过期时间,过期后Token将失效,需要重新认证获取新的Token。
HTTPS:
• 在传输过程中使用HTTPS,防止Token在传输过程中被截获。
尚庭公寓后台管理系统登陆流程

根据上述图片我们可以分析出总共需要完成三个接口,以下是接口定义以及接口代码
获取图形验证码
导入maven
依赖
xml
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置application.yml
xml
spring:
data:
redis:
host: <hostname>
port: <port>
database: 0
controller
层
java
package com.nie.lease.web.admin.controller.login;
import com.nie.lease.common.login.LoginUserHolder;
import com.nie.lease.common.result.Result;
import com.nie.lease.common.utils.JwtUtils;
import com.nie.lease.web.admin.service.LoginService;
import com.nie.lease.web.admin.vo.login.CaptchaVo;
import com.nie.lease.web.admin.vo.login.LoginVo;
import com.nie.lease.web.admin.vo.system.user.SystemUserInfoVo;
import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Tag(name = "后台管理系统登录管理")
@RestController
@RequestMapping("/admin")
public class LoginController {
@Autowired
private LoginService service;
@Operation(summary = "获取图形验证码")
@GetMapping("login/captcha")
public Result<CaptchaVo> getCaptcha() {
CaptchaVo result=service.getCaptcha();
return Result.ok(result);
}
service
接口
java
package com.nie.lease.web.admin.service;
import com.nie.lease.web.admin.vo.login.CaptchaVo;
import com.nie.lease.web.admin.vo.login.LoginVo;
import com.nie.lease.web.admin.vo.system.user.SystemUserInfoVo;
public interface LoginService {
CaptchaVo getCaptcha();
}
service
实现类
java
@Override
public CaptchaVo getCaptcha() {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
String code = specCaptcha.text().toLowerCase();
String key = RedisConstant.ADMIN_LOGIN_PREFIX + UUID.randomUUID();
stringRedisTemplate.opsForValue().set(key,code,RedisConstant.ADMIN_LOGIN_CAPTCHA_TTL_SEC, TimeUnit.SECONDS);
return new CaptchaVo(specCaptcha.toBase64(),key);
}
测试结果如下:
登录
导入maven
依赖
xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
创建Jwt工具类
java
package com.nie.lease.common.utils;
import com.nie.lease.common.exception.LeaseException;
import com.nie.lease.common.result.ResultCodeEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtUtils {
private static SecretKey tokenSignKey = Keys.hmacShaKeyFor("M0PKKI6pYGVWWfDZw90a0lTpGYX1d4AQ".getBytes());
public static String createToken(Long userId, String username) {
String token = Jwts.builder().
setSubject("USER_INFO").
setExpiration(new Date(System.currentTimeMillis() + 3600000)).
claim("userId", userId).
claim("username", username).
signWith(tokenSignKey).
compact();
return token;
}
public static void main(String[] args) {
System.out.println(createToken(2L, "user"));
}
public static Claims parseToken(String token){
if (token==null){
throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
}
try {
JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(tokenSignKey).build();
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
return claimsJws.getBody();
}catch (ExpiredJwtException e){
throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);
}catch (JwtException e){
throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);
}
}
}
controller
层
java
@Operation(summary = "登录")
@PostMapping("login")
public Result<String> login(@RequestBody LoginVo loginVo) {
String token = service.login(loginVo);
return Result.ok(token);
}
service
接口
java
String login(LoginVo loginVo);
service
实现类
java
@Override
public String login(LoginVo loginVo) {
//1.判断是否输入了验证码
if (!StringUtils.hasText(loginVo.getCaptchaCode())) {
throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOT_FOUND);
}
//2.校验验证码
String code = stringRedisTemplate.opsForValue().get(loginVo.getCaptchaKey());
if (code == null) {
throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_EXPIRED);
}
if (!code.equals(loginVo.getCaptchaCode().toLowerCase())) {
throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_ERROR);
}
//3.校验用户是否存在
SystemUser systemUser = systemUserMapper.selectOneByUsername(loginVo.getUsername());
if (systemUser == null) {
throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_NOT_EXIST_ERROR);
}
//4.校验用户是否被禁
if (systemUser.getStatus() == BaseStatus.DISABLE) {
throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);
}
//5.校验用户密码
if (!systemUser.getPassword().equals(DigestUtils.md5Hex(loginVo.getPassword()))) {
throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_ERROR);
}
//6.创建并返回TOKEN
return JwtUtils.createToken(systemUser.getId(), systemUser.getUsername());
}
mapper
接口
java
SystemUser selectOneByUsername(String username);
mapper
xml文件
java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nie.lease.web.admin.mapper.SystemUserMapper">
<select id="pageSystemUser" resultType="com.nie.lease.web.admin.vo.system.user.SystemUserItemVo">
select su.id,
username,
su.name,
type,
phone,
avatar_url,
additional_info,
post_id,
su.status,
sp.name post_name
from system_user su
left join system_post sp on su.post_id = sp.id and sp.is_deleted = 0
<where>
su.is_deleted = 0
<if test="queryVo.name != null and queryVo.name != ''">
and su.name like concat('%',#{queryVo.name},'%')
</if>
<if test="queryVo.phone !=null and queryVo.phone != ''">
and su.phone like concat('%',#{queryVo.phone},'%')
</if>
</where>
</select>
<select id="selectOneByUsername" resultType="com.nie.lease.model.entity.SystemUser">
select id,
username,
password,
name,
type,
phone,
avatar_url,
additional_info,
post_id,
status
from system_user
where is_deleted = 0
and username = #{username}
</select>
</mapper>
编写拦截器
书写解析token的方法
java
package com.nie.lease.common.utils;
import com.nie.lease.common.exception.LeaseException;
import com.nie.lease.common.result.ResultCodeEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtUtils {
private static SecretKey tokenSignKey = Keys.hmacShaKeyFor("M0PKKI6pYGVWWfDZw90a0lTpGYX1d4AQ".getBytes());
public static String createToken(Long userId, String username) {
String token = Jwts.builder().
setSubject("USER_INFO").
setExpiration(new Date(System.currentTimeMillis() + 3600000)).
claim("userId", userId).
claim("username", username).
signWith(tokenSignKey).
compact();
return token;
}
public static void main(String[] args) {
System.out.println(createToken(2L, "user"));
}
public static Claims parseToken(String token){
if (token==null){
throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
}
try {
JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(tokenSignKey).build();
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
return claimsJws.getBody();
}catch (ExpiredJwtException e){
throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);
}catch (JwtException e){
throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);
}
}
}
##### 编写拦截器
java
package com.nie.lease.web.admin.custom.interceptor;
import com.nie.lease.common.exception.LeaseException;
import com.nie.lease.common.login.LoginUser;
import com.nie.lease.common.login.LoginUserHolder;
import com.nie.lease.common.result.ResultCodeEnum;
import com.nie.lease.common.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("access-token");
Claims claims = JwtUtils.parseToken(token);
Long userId = claims.get("userId", Long.class);
String userName = claims.get("userName", String.class);
LoginUserHolder.setLoginUser(new LoginUser(userId, userName));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LoginUserHolder.clear();
}
}
注册拦截器
java
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private StringToBaseEnumConverterFactory stringToBaseEnumConverterFactory;
@Autowired
private AuthenticationInterceptor authenticationInterceptor;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(this.stringToBaseEnumConverterFactory);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.authenticationInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/login/**");
}
}