相信大家如果是按照文章顺序看下来的话在day3-3的那篇文章中已经看过rememberme的相关介绍了,这里我就不再重复,主要来看实战效果
以下的yaml配置设置了会话若一分钟没有任何操作则认为过期,需要重新进行认证,当然默认是30分钟
server:
reactive:
session:
timeout: 1
http.rememberMe()
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth->auth.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.csrf().disable()
.formLogin(Customizer.withDefaults())
.rememberMe()
.and().build();
}
开启之后我们就会看到出现一个多选框进行remember me的询问了

如果你没有选择记住我的勾选1分钟后(默认30分钟)就会让你重新认证
勾选登录后查看Cookie可以明显看到信息

我们可以采用PersistentTokenBasedRememberMeServices的形式修改默认的TokenBasedRememberMeServices,这种实现形式更加安全,在重新获取认证或系统自动重新认证时会更新存储的在Cookie中的remember-me,且解码出来不包含用户名,达到更高的安全性
@Bean
public RememberMeServices rememberMeServices(){
return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),userDetailsService,new InMemoryTokenRepositoryImpl())
}
http.rememberMe()
.rememberMeServices(rememberMeServices())
这里说一下使用InMemoryTokenRepositoryImpl显然是一种基于内存的实现,如果重启程序我们就会再次进行重新认证,实际上常用的方式是基于数据库的实现JdbcTokenRepositoryImpl
采用以下方式即可,无需重新注入.rememberMeServices(rememberMeServices())
http.rememberMe()
.tokenRepository(new JdbcTokenRepositoryImpl())
在前后端分离的生态下,前端后端并非一个系统,所以相应来说并没有传统Web那么简单,而且也不需要我们配置rememberMe,而是采用令牌和前端的共同设置,但实际上实现的思想是和PersistentTokenBasedRememberMeServices的实现方式相同
我们简单介绍一下流程
| 技术 | 版本 |
|---|---|
| Vue | 3 |
| typescript | - |
| axios | 0.28 |

//导入axios
import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios'
export class Request{
public static axiosInstance:AxiosInstance
public static init(){
this.axiosInstance = axios.create({
baseURL:'http://localhost:8050/',
timeout:1000
})
this.initInterceptors()
return this.axiosInstance
}
public static initInterceptors(){
this.axiosInstance.interceptors.request.use(
(config:AxiosRequestConfig)=>{
const token = window.localStorage.getItem('token')
if(token!=null&&config?.headers){
console.log(token)
//后续采用将token作为请求头中的参数进行向后端请求
config.headers.token = token
}
return config
},
(error:any)=>{
console.log(error)
}
)
this.axiosInstance.interceptors.response.use(
(response:AxiosResponse)=>{
if(response.status===200){
return response
}else{
this.errorHandle(response)
return null
}
},
(error:any)=>{
this.errorHandle(error)
}
)
}
public static errorHandle(res:any){
console.log(res)
}
}
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import {Request} from '@/request/index'
const app = createApp(App)
app.config.globalProperties.$http = Request.init()
app.use(router)
app.mount('#app')
<template>
<div id="nav">
<input type="text" v-model="form.username" name="username"><br>
<input type="password" v-model="form.password" name="password"><br>
<button @click="login">登录</button>
<button @click="getIndex">index</button>
</div>
<router-view />
</template>
<script lang="ts" setup>
import { reactive } from '@vue/reactivity'
import { getCurrentInstance } from 'vue'
const { proxy }: any = getCurrentInstance()
const form = reactive({
username: '',
password: '',
//这里单纯只是我图方便这样写
rememberMe: true
})
const login = async () => {
const { data } = await proxy.$http.post('/login', form)
//前端存入localStorage中
window.localStorage.setItem('token', data)
console.log(data)
}
const getIndex = async () => {
const { data } = await proxy.$http.get('/test')
console.log(data)
}
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
server:
reactive:
session:
timeout: 1
port: 8050
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring_security_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&allowPublicKeyRetrieval=true
username: root
password: $hsduiwzhxajhd-sadha
<dependencies>
<dependency>
<groupId>cn.fly</groupId>
<artifactId>JJWTUtil</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
package com.example.rememberme.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "user_role_con")
public class UserRoleCon {
private int id;
@TableField(value = "userId")
private long userId;
@TableField(value = "roleId")
private long roleId;
}
需要实现UserDetails接口
package com.example.rememberme.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "users")
public class User implements UserDetails, Serializable {
@TableId(value = "userId")
private long userId;
private String username;
private String password;
private boolean enabled;
private boolean expired;
@TableField(value = "tokenExpired")
private boolean tokenExpired;
private boolean locked;
@TableField(exist = false)
private ArrayList<Role> roles = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
HashSet<SimpleGrantedAuthority> authorities = new HashSet<>();
roles.forEach(x->{
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(x.getRoleName());
authorities.add(simpleGrantedAuthority);
});
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return expired;
}
@Override
public boolean isAccountNonLocked() {
return locked;
}
@Override
public boolean isCredentialsNonExpired() {
return tokenExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
public long getUserId() {
return userId;
}
public void setUserId(long userId) {
this.userId = userId;
}
public ArrayList<Role> getRoles() {
return roles;
}
public void setRoles(ArrayList<Role> roles) {
this.roles = roles;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setExpired(boolean expired) {
this.expired = expired;
}
public void setTokenExpired(boolean tokenExpired) {
this.tokenExpired = tokenExpired;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
}
package com.example.rememberme.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "role")
public class Role {
@TableId(value = "roleId")
private long roleId;
@TableField(value = "roleName")
private String roleName;
@TableField(value = "roleAnnotation")
private String roleAnnotation;
}
package com.example.rememberme.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.rememberme.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
package com.example.rememberme.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.rememberme.entity.UserRoleCon;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserRoleConMapper extends BaseMapper<UserRoleCon> {
}
package com.example.rememberme.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.rememberme.entity.Role;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}
package com.example.rememberme.controller;
import com.example.rememberme.param.LoginParam;
import com.example.rememberme.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoginController {
//注入
private final UserService userService;
@Autowired
public LoginController(UserService userService) {
this.userService = userService;
}
//登录接口使用remember-me
@PostMapping("/login")
public String Login(@RequestBody LoginParam loginParam) {
return userService.login(loginParam);
}
}
package com.example.rememberme.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
public class TestController {
@PreAuthorize("hasAuthority('ROLE_admin')")
@GetMapping ("/test")
public String Test(){
return "test ok";
}
}
package com.example.rememberme.param;
import com.example.rememberme.entity.User;
import lombok.Data;
import java.io.Serializable;
@Data
public class LoginParam extends User implements Serializable {
private boolean rememberMe;
}
package com.example.rememberme.util;
import cn.fly.jjwtutil.JJWTUtil;
import org.springframework.stereotype.Component;
@Component
public class JWTUtil extends JJWTUtil {
}
package com.example.rememberme.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.rememberme.entity.User;
import com.example.rememberme.param.LoginParam;
public interface UserService extends IService<User> {
String login(LoginParam loginParam);
}
package com.example.rememberme.service.impl;
import cn.fly.jjwtutil.JWTDefaultExpirationTime;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.rememberme.entity.User;
import com.example.rememberme.mapper.UserMapper;
import com.example.rememberme.param.LoginParam;
import com.example.rememberme.service.UserService;
import com.example.rememberme.util.JWTUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Service;
import java.util.Objects;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
private final JWTUtil jwtUtil;
private final AuthenticationManager authenticationManager;
@Autowired
public UserServiceImpl(JWTUtil jwtUtil, AuthenticationManager authenticationManager) {
this.jwtUtil = jwtUtil;
this.authenticationManager = authenticationManager;
}
//替换原始流程中的UsernamePasswordAuthenticationFilter
@Override
public String login(LoginParam loginParam) {
//无需对方法进行判断,无需进行json转换
String username = loginParam.getUsername();
String password = loginParam.getPassword();
boolean rememberMe = loginParam.isRememberMe();
//传递到UsernamePasswordAuthenticationFilter中使用loadUserByUsername进行认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
//1.获取AuthenticationManager
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)){
throw new UsernameNotFoundException("用户名或密码错误");
}
//认证成功构建token令牌
//判断remember
if(!rememberMe){
//未使用则不采用默认构建
System.out.println("remember me not use!");
//这里是设置了一天的登录有效凭证,当然我们也会在退出的时候进行清除,后续JJWTUtil中我设置临时凭证的颁布
//获取让前端颁布sessionStorage的会话存储作为实现
jwtUtil.initExpirationTime(JWTDefaultExpirationTime.DAY);
}else{
jwtUtil.defaultBuilder(jwtUtil);
}
Claims claims = Jwts.claims();
User principal = (User) authenticate.getPrincipal();
claims.put("userId",principal.getUserId());
String token = jwtUtil.createToken(claims);
return token;
}
}
对用户的登录信息进行获取校验登录
package com.example.rememberme.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.rememberme.entity.Role;
import com.example.rememberme.entity.User;
import com.example.rememberme.entity.UserRoleCon;
import com.example.rememberme.mapper.RoleMapper;
import com.example.rememberme.mapper.UserMapper;
import com.example.rememberme.mapper.UserRoleConMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Service
public class DefineUserDetailsServiceImpl implements UserDetailsService {
//注入Mapper
private final UserMapper userMapper;
private final RoleMapper roleMapper;
private final UserRoleConMapper userRoleConMapper;
@Autowired
public DefineUserDetailsServiceImpl(UserMapper userMapper, RoleMapper roleMapper, UserRoleConMapper userRoleConMapper) {
this.userMapper = userMapper;
this.roleMapper = roleMapper;
this.userRoleConMapper = userRoleConMapper;
}
//认证自动调用本方法进行加载目标用户
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//通过username进行查询获取用户信息
User user = this.getUserByUserName(username);
if (Objects.isNull(user)){
throw new UsernameNotFoundException("用户名无法获取");
}
long userId = user.getUserId();
//主要获取权限信息
ArrayList<Role> roleList = this.getAuthoritiesByUserId(userId);
//封装到GrantedAuthority
user.setRoles(roleList);
return user;
}
private ArrayList<Role> getAuthoritiesByUserId(long userId) {
LambdaQueryWrapper<UserRoleCon> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserRoleCon::getUserId,userId).select(UserRoleCon::getRoleId);
List<UserRoleCon> userRoleCons = userRoleConMapper.selectList(queryWrapper);
ArrayList<Role> roles = new ArrayList<>();
for (UserRoleCon userRoleCon : userRoleCons) {
LambdaQueryWrapper<Role> queryWrapper1 = new LambdaQueryWrapper<>();
queryWrapper1.eq(Role::getRoleId,userRoleCon.getRoleId()).last("limit 1");
Role role = roleMapper.selectOne(queryWrapper1);
roles.add(role);
}
return roles;
}
private User getUserByUserName(String username){
System.out.println("user-"+username);
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
userLambdaQueryWrapper.eq(User::getUsername,username).last("limit 1");
return userMapper.selectOne(userLambdaQueryWrapper);
}
}
在UsernamePasswordAuthenticationFilter前抢先采用token的解析等方式提供一个SecurityContextHolder,以表示用户已认证
package com.example.rememberme.filter;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.rememberme.entity.User;
import com.example.rememberme.mapper.UserMapper;
import com.example.rememberme.util.JWTUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static net.sf.jsqlparser.util.validation.metadata.NamedObject.user;
//认证判断过滤器
@Component
public class AuthenicationTokenFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
private UserMapper userMapper;
@Autowired
public AuthenicationTokenFilter(JWTUtil jwtUtil, UserMapper userMapper) {
this.jwtUtil = jwtUtil;
this.userMapper = userMapper;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
//token为空直接放行
if (!StringUtils.hasText(token)){
filterChain.doFilter(request,response);
return;
}
//不为空进行解析
Claims tokenPayLoad = jwtUtil.getTokenPayLoad(token);
String userId = String.valueOf( tokenPayLoad.get("userId"));
System.out.println(userId);
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserId,userId).last("limit 1");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
// 存入SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request,response);
}
}
实现AccessDeniedHandler
package com.example.rememberme.handler;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
@Component
public class AuthenticationNotFoundHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
String message = accessDeniedException.getMessage();
HashMap<String, String> data = new HashMap<>();
data.put("code", String.valueOf(403));
data.put("exceptionMsg",message);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(data);
response.getWriter().flush();
response.getWriter().close();
}
}
package com.example.rememberme.config;
import com.example.rememberme.filter.AuthenicationTokenFilter;
import com.example.rememberme.handler.AuthenticationNotFoundHandler;
import com.example.rememberme.service.UserService;
import com.example.rememberme.service.impl.DefineUserDetailsServiceImpl;
import com.example.rememberme.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.*;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.UUID;
@EnableWebSecurity
@Configuration
@EnableMethodSecurity
public class SpringSecurityConfig {
private final AuthenticationNotFoundHandler authenticationNotFoundHandler;
private final DefineUserDetailsServiceImpl defineUserDetailsService;
private final AuthenicationTokenFilter authenicationTokenFilter;
@Autowired
public SpringSecurityConfig(DefineUserDetailsServiceImpl defineUserDetailsService, AuthenicationTokenFilter authenicationTokenFilter,AuthenticationNotFoundHandler authenticationNotFoundHandler) {
this.defineUserDetailsService = defineUserDetailsService;
this.authenicationTokenFilter = authenicationTokenFilter;
this.authenticationNotFoundHandler = authenticationNotFoundHandler;
}
//跨域处理
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
corsConfiguration.setMaxAge(3600L);
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
//获取AuthenticationManager(认证管理器),登录时认证使用
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(auth -> auth
.mvcMatchers("/login").anonymous()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.userDetailsService(defineUserDetailsService)
.addFilterBefore(authenicationTokenFilter,UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.accessDeniedHandler(authenticationNotFoundHandler)
.and()
.cors()
.configurationSource(corsConfigurationSource())
.and()
.build();
}
}
前端信息打印


后端信息打印

默认token7天后过期,在7天中免登录