『博客开发日记』之登录接口的实现

本文最后更新于 2026年1月11日 晚上

登录接口的实现


登录接口的需求

登录使用前台和后台的认证授权统一都使用SpringSecurity安全框架来实现

需要实现登录功能

有些功能必须登录后才能使用,未登录状态是不能使用的

登录分析

1.自定义登录接口

调用ProviderManager的方法进行认证 如果认证通过生成jwt 把用户信息存入redis中

2.自定义UserDetailsService

在这个实现类中去查询数据库 注意配置passwordEncoder为BCryptPasswordEncoder

校验:

定义Jwt认证过滤器

获取token 解析token获取其中的userid 从redis中获取用户信息 存入SecurityContextHolder


前提准备

先添加依赖


添加需要的工具类


代码实现

先创建BlogLoginController类

编写登录接口方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//登录接口
@RestController
public class BlogLoginController
{
@Autowired
private BlogLoginService blogLoginService;

@PostMapping("/login")
public ResponseResult login(@RequestBody User user)
{
return blogLoginService.login(user);
}

}

然后创建BlogLoginService接口并在其中创建需要的方法


再把BlogLoginService接口的实现类创建出来

核心业务逻辑在这个实现类中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 登录认证
@Service
public class BlogLoginServiceImpl implements BlogLoginService
{

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private RedisCache redisCache;

@Override
public 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生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);//传数据进去jwt加密,最终生成一个字符串类型的jwt
//把用户信息存入Redis (格式为 bloglogin:id)
redisCache.setCacheObject("bloglogin:" + userId, loginUser);

//把token和userInfo封装,返回
//把User转化成UserInfoVo
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
BlogUserLoginVo vo = new BlogUserLoginVo(jwt, userInfoVo);
return ResponseResult.okResult(vo);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 登录认证
@Service
public class BlogLoginServiceImpl implements BlogLoginService
{

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private RedisCache redisCache;

@Override
public 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生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);//传数据进去jwt加密,最终生成一个字符串类型的jwt
//把用户信息存入Redis (格式为 bloglogin:id)
redisCache.setCacheObject("bloglogin:" + userId, loginUser);

//把token和userInfo封装,返回
//把User转化成UserInfoVo
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
BlogUserLoginVo vo = new BlogUserLoginVo(jwt, userInfoVo);
return ResponseResult.okResult(vo);
}
}

修改SecurityConfig类配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//该类的作用
//启用 Spring Security。
//定义哪些接口可以匿名访问(白名单)。
//其他接口必须认证。
//禁用 Session,适合前后端分离 + Token 认证场景。
//预留了异常处理和自定义过滤器的位置。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login").anonymous()
// 除上面外的所有请求不需要认证就可以访问
.anyRequest().permitAll();

http.logout().disable();
//允许跨域
http.csrf();
}

//对密码进行加密
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationManager authenticationManager() throws Exception
{
return super.authenticationManager();
}
}

创建UserDetailServiceImpl类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
public class UserDetailServiceImpl implements UserDetailsService
{

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
//根据用户名查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();// 查询条件
queryWrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(queryWrapper);
//判断是否查到用户,如果没查到抛出异常
if (Objects.isNull(user)){
throw new RuntimeException("用户不存在");
}

//如果查到用户,返回用户信息
// TODO 查询权限信息封装
return new LoginUser(user);
}
}

创建LoginUser实体类

实现其中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails
{
private User user;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}

@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;
}
}

分别创建BlogUserLoginVo和UserInfoVo对数据进行封装

BlogUserLoginVo里

1
2
3
4
5
6
7
8
9
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BlogUserLoginVo
{
private String token;
private UserInfoVo userInfo;
}

UserInfoVo里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
@Accessors(chain = true)
public class UserInfoVo {
/**
* 主键
*/
private Long id;
/**
* 昵称
*/
private String nickName;
/**
* 头像
*/
private String avatar;
private String sex;
private String email;
}


登录校验过滤器代码实现

定义Jwt认证过滤器

获取token 解析token获取其中的userid

从redis中获取用户信息 存入SecurityContextHolder


创建JwtAuthenticationTokenFilter类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//jwt认证过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private RedisCache redisCache;

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException
{
//获取请求头中的token
String token = httpServletRequest.getHeader("token");
if (!StringUtils.hasText(token)){
//如果没有token,说明该接口不需要登录,直接放行
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
//解析获取userId
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//token 超时,token 非法
//响应告诉前端要重新登录
ResponseResult responseResult = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(responseResult));//将传入的数据转化为json格式
return;
}
String userId = claims.getSubject();
//从redis中获取用户信息
LoginUser loginUser = redisCache.getCacheObject("bloglogin:" + userId);
//如果获取不到,提示重新登录
if (Objects.isNull(loginUser)){
ResponseResult responseResult = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(responseResult));//将传入的数据转化为json格式
return;
}

//存入SecurityContextHolder
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, null);//已认证过,传三个参数
SecurityContextHolder.getContext().setAuthentication(authentication);

//放行
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}

认证授权异常处理

通过自定义异常处理器来实现符合项目接口规范的响应的格式

自定义以下两种处理器

AuthenticationEntryPoint 认证失败处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//认证失败处理器
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint
{

@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException
{
e.printStackTrace();//打印异常信息
//.InsufficientAuthenticationException,权限不足,或需要登录
//.BadCredentialsException,用户名或密码错误
ResponseResult result = null;
if(e instanceof BadCredentialsException){
result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(), e.getMessage());//登录错误
}else if(e instanceof InsufficientAuthenticationException){
result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);//权限不足,或需要登录
}else {
result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), "认证或授权失败");
}
//响应给前端
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
}
}

AccessDeniedHandler 授权失败处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//授权失败处理器
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler
{

@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException
{
e.printStackTrace();//打印异常信息
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);//授权失败
//响应给前端
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));

}
}

统一异常处理

开发过程中可能需要做很多的判断校验,如果出现了非法情况需要响应对应的提示

但是如果我们每次都自己手动去处理就会非常麻烦

所以可以选择直接抛出异常的方式对异常进行统一处理

把异常中的信息封装成ResponseResult响应给前端


自定义异常抛出处理类SystemException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//全局异常处理类
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler
{
@ExceptionHandler(SecurityException.class)
public ResponseResult systemExceptionHandler(SystemException exception)
{
//打印异常信息
log.error("出现异常!!! {}", exception);
//从异常对象中获取异常提示信息并封装返回
return ResponseResult.errorResult(exception.getCode(), exception.getMessage());
}

//对其他异常进行处理
@ExceptionHandler(Exception.class)
public ResponseResult exceptionHandler(Exception exception)
{
//打印异常信息
log.error("出现异常!!! {}", exception);
//从异常对象中获取异常提示信息并封装返回
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), exception.getMessage());
}
}

PS:该系列只做为作者学习开发项目做的笔记用

不一定符合读者来学习,仅供参考


预告

后续会记录博客的开发过程

每次学习会做一份笔记来进行发表

“一花一世界,一叶一菩提”


版权所有 © 2025 云梦泽
欢迎访问我的个人网站:https://hgt12.github.io/


『博客开发日记』之登录接口的实现
http://example.com/2026/01/07/『博客开发日记』之登录接口的实现/
作者
云梦泽
发布于
2026年1月7日
更新于
2026年1月11日
许可协议