详解前后端分离使用Token作为标识Shiro认证登录

思考

  • 为什么需要用token来做?传统的session为什么不可以?token有何优势。

    1. session存在的问题 :①前后端分离项目,前端可能是web/app等,对于存储sessionId的cookie问题;②session存在CSRF跨站伪造请求攻击;③ 服务器压力增大,通常session存储在内存中,用户量大服务器压力也大;③ 服务器分布式部署情况下,session就会不一定获取的到,存在不在一台服务器中的情况,拓展性很差。

    2. token有何优势 :token与session的不同主要:

      ①认证成功后,会对当前用户数据进行加密,生成一个加密字符串token,返还给客户端**(服务器端并不进行保存)**

      ②浏览器会将接收到的token值存储在Local Storage中,(通过js代码写入Local Storage,通过js获取,并不会像cookie一样自动携带)

      ③再次访问时服务器端对token值的处理:服务器对浏览器传来的token值进行解密,解密完成后进行用户数据的查询,如果查询成功,则通过认证,实现状态保持,所以,即时有了多台服务器,服务器也只是做了token的解密和用户数据的查询,它不需要在服务端去保留用户的认证信息或者会话信息,这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利,解决了session扩展性的弊端。

一、登录流程

① 用户输入用户名密码来通过数据库验证,这里不通过shiro(主体的login方法)登录,并把token创建的时间戳存储在redis中把AUTH_REFRESH_TOKEN + jwtId作为redis的key。

② 由于第一次登录验证,需要将token从Controller层返回给前端用于存储到localStorge中。

③ 客户端访问服务器时请求头携带token,传递到后端shiro访问控制过滤器(AccessControlFilter)进行拦截使用shiro的login方法进行认证授权,在realm中查询角色权限授权

  • 后续请求可通过继承shiro访问控制过滤器设置响应头带有token传递给客服端

  • token过期操作:在shiro(AccessControlFilter)中访问许可(isAccessAllowed)中处理jwt过期异常,如token一小时过期,这时在异常处理中上锁(防多刷),再去判断token自己的时间戳和redis之前存储的时间戳是否相等,再颁发新的token,设置新的响应头(给客服端刷新localStorge中的token),这时我们在redis中存储一个30s的即将过期的token,放多次刷新token,该用户token过期将从redis中取这个token进行授权就无需刷新token了

二、服务器代码实现

配置及工具类

yml

#custom config
config:
    # JWT认证加密私钥
    encrypt-secret: 6Dx8SIuaHXJYnpsG18SSpjPs50lZcT52
    # AccessToken过期时间(秒)
    access-token-expire-time: 600
    # RefreshToken过期时间(秒)
    refresh-token-expire-time: 3600

JWTToken(继承AuthenticationToken)

public class JWTToken implements AuthenticationToken {

    /**
     * 凭证
     */
    private final String accessToken;

    public JWTToken(String accessToken) {
        this.accessToken = accessToken;
    }

    @Override
    public Object getPrincipal() {
        return accessToken;
    }

    @Override
    public Object getCredentials() {
        return accessToken;
    }

JWTUtils

/**
 * JWT加密密钥
 */
private static String ENCRYPT_SECRET;

/**
 * AccessToken过期时间(秒)
 */
private static long ACCESS_TOKEN_EXPIRE_TIME;

/**
 * 扩展字段 isAdmin
 */
private static final String EXTEND_IS_ADMIN = "isAdmin";

/**
 * 扩展字典 时间戳
 */
private static final String EXTEND_TIMESTAMP = "timestamp";

/**
 * 生成jwt
 *
 * @param jwtId             jwt标识
 * @param subjectId         主体id
 * @param isAdmin           是否为管理员
 * @param currentTimeMillis 签发时间
 * @return jwt字符串
 */
public static String generate(String jwtId, String subjectId, boolean isAdmin, String currentTimeMillis) {
    Algorithm algorithm = Algorithm.HMAC256(ENCRYPT_SECRET);
    return JWT.create()
        //扩展字段
        .withClaim(EXTEND_IS_ADMIN, isAdmin)
        .withClaim(EXTEND_TIMESTAMP, currentTimeMillis)
        //jwt标识
        .withJWTId(jwtId)
        //主体id
        .withSubject(subjectId)
        //签发时间
        .withIssuedAt(new Date(Long.parseLong(currentTimeMillis)))
        //有效时间
        .withExpiresAt(new Date(Long.parseLong(currentTimeMillis) + ACCESS_TOKEN_EXPIRE_TIME * 1000))
        //签名
        .sign(algorithm);
}

/**
 * 验证jwt
 *
 * @param jwt jwt字符串
 */
public static void verify(String jwt) {
    Algorithm algorithm = Algorithm.HMAC256(ENCRYPT_SECRET);
    JWTVerifier verifier = JWT.require(algorithm).build();
    verifier.verify(jwt);
}

/**
 * 获取jwt标识
 *
 * @param jwt jwt字符串
 * @return jwt标识
 */
public static String getId(String jwt) {
    DecodedJWT decodedJWT = JWT.decode(jwt);
    return decodedJWT.getId();
}

/**
 * 获取主体id
 *
 * @param jwt jwt字符串
 * @return 主体id
 */
public static String getSubjectId(String jwt) {
    DecodedJWT decodedJWT = JWT.decode(jwt);
    return decodedJWT.getSubject();
}

/**
 * 是否为管理员
 *
 * @param jwt jwt字符串
 * @return 是否为管理员
 */
public static boolean isAdmin(String jwt) {
    return getClaim(jwt, EXTEND_IS_ADMIN).asBoolean();
}

/**
 * 获取时间戳
 *
 * @param jwt jwt字符串
 * @return 时间戳
 */
public static String getTimestamp(String jwt) {
    return getClaim(jwt, EXTEND_TIMESTAMP).asString();
}

/**
 * 解析jwt
 *
 * @param jwt   jwt字符串
 * @param claim 声明字段
 * @return 字段内容
 */
public static Claim getClaim(String jwt, String claim) {
    DecodedJWT decodedJWT = JWT.decode(jwt);
    return decodedJWT.getClaim(claim);
}


@Value("${config.access-token-expire-time}")
public void setAccessTokenExpireTime(long accessTokenExpireTime) {
    ACCESS_TOKEN_EXPIRE_TIME = accessTokenExpireTime;
}

@Value("${config.encrypt-secret}")
public void setEncryptSecret(String encryptSecret) {
    ENCRYPT_SECRET = encryptSecret;
}

1. 验证用户名、密码

登录验证控制器
/**
 * 账号密码登录
 *
 * @param loginName  账号/手机号
 * @param password   密码
 * @param captchaKey 图形验证码key
 * @param captcha    图形验证码
 */
@PostMapping("/accountLogin")
public ResponseEntity accountLogin(@RequestParam String loginName, @RequestParam String password,
                                   @RequestParam String captchaKey, @RequestParam String captcha,
                                   HttpServletRequest request) {
    if (StringUtils.isAnyBlank(loginName, password)) {
        return ResponseEntity.error("用户名和密码不能为空.");
    }
    if (!CodeValidator.checkCaptcha(captchaKey, captcha)) {
        return ResponseEntity.error("验证码错误.");
    }
    UserCredentialEntity credential = credentialService.getCredentialByLoginName(loginName);
    if (credential == null) {
        credential = credentialService.getCredentialByPhone(loginName);
    }
    if (credential != null) {
        if (BCrypt.checkpw(password, credential.getPassword())) {
            //更新登录信息
            credentialService.updateLastLoginInfo(loginName, ServletUtil.getClientIP(request), new Date());
            //签发accessToken
            String accessToken = issueAccessToken(credential.getId(), false);
            return ResponseEntity.ok(accessToken, "登录成功");
        }
    }
    return ResponseEntity.error("登录失败,账号密码错误.");
颁发token
/**
 * 颁发AccessToken
 *
 * @param subjectId 用户id
 * @return accessToken
 */
private String issueAccessToken(String subjectId, boolean isAdmin) {
    //jwt标识
    String jwtId = UUID.randomUUID().toString(true);
    //设置RefreshToken,时间戳为当前时间戳
    String currentTimeMillis = String.valueOf(System.currentTimeMillis());
    RedisUtils.set(RedisKeys.AUTH_REFRESH_TOKEN + jwtId, currentTimeMillis, refreshTokenExpireTime);
    //生成accessToken
    return JWTUtils.generate(jwtId, subjectId, isAdmin, currentTimeMillis);
}
  • 返回给客户端,客户端查看response返回的token存储localstorge,接着发送token请求

2. token的验证认证和过期情况

shiroConfig
@Configuration
public class ShiroConfig {

    /**
     * 安全过滤器
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        // 添加认证过滤器
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        // 配置拦截规则
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //无需登录即可访问的url
        //登录
        filterChainDefinitionMap.put("/accountLogin", "anon");
        filterChainDefinitionMap.put("/smsLogin", "anon");
        filterChainDefinitionMap.put("/alogin", "anon");
        //注销
        filterChainDefinitionMap.put("/logout", "anon");
        //图形化验证码
        filterChainDefinitionMap.put("/captcha", "anon");
        //短信验证码
        filterChainDefinitionMap.put("/vcode", "anon");
        //上传文件下载
        filterChainDefinitionMap.put("/filestore/**", "anon");
        //网站门户(包含注册、找回密码等url)
        filterChainDefinitionMap.put("/portal/**", "anon");
        //支付回调
        filterChainDefinitionMap.put("/alipay/callback", "anon");
        filterChainDefinitionMap.put("/alipay/notify", "anon");
        //静态html页面
        filterChainDefinitionMap.put("/**/*.html", "anon");

        //其余url需要进行accessToken验证
        filterChainDefinitionMap.put("/**", "jwt");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /**
     * 安全管理
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关闭Shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //多种方式认证
        securityManager.setAuthenticator(modularRealmAuthenticator());
        List<Realm> realms = new ArrayList<>();
        realms.add(jwtRealm());
        securityManager.setRealms(realms);
        return securityManager;
    }

    /**
     * 多方式认证
     */
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator() {
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

    /**
     * token认证
     */
    @Bean
    public JWTRealm jwtRealm() {
        JWTRealm jwtRealm = new JWTRealm();
        jwtRealm.setCredentialsMatcher(new AllowAllCredentialsMatcher());
        return jwtRealm;
    }

    /**
     * 开启注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * setUsePrefix(true)用于解决一个奇怪的bug。在引入spring aop的情况下。
     * 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
     * 加入这项配置能解决这个bug
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setUsePrefix(true);
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}
JWTFilter
public class JWTFilter extends AccessControlFilter {

    /**
     * RefreshToken过期时间(秒)
     */
    @Value("${config.refresh-token-expire-time}")
    private long refreshTokenExpireTime;

    /**
     * accessToken key
     */
    private final String ACCESS_TOKEN_KEY = "Authorization";

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        String accessToken = getAccessToken(request);
        if (StringUtils.isBlank(accessToken)) {
            return false;
        }
        try {
            getSubject(request, response).login(new JWTToken(accessToken));
        } catch (Exception ex) {
            Console.log(ex.getMessage());
            //如果token过期则尝试刷新token
            Throwable throwable = ex.getCause();
            if (throwable instanceof TokenExpiredException) {
                return this.refreshToken(accessToken, request, response);
            }
            return false;
        }
        return true;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletResponse httpResponse = WebUtils.toHttp(servletResponse);
        ObjectMapper mapper = new ObjectMapper();
        String responseBody = mapper.writeValueAsString(ResponseEntity.error(ErrorCode.AUTH_FAILED));
        ServletUtil.write(httpResponse, responseBody, "application/json; charset=utf-8");
        return false;
    }


    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        httpServletResponse.setHeader("Access-Control-Expose-Headers", ACCESS_TOKEN_KEY);
        httpServletResponse.setHeader("Cache-Control", "no-store");
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 刷新token,该方法为同步方法同一accessToken在同一时间只能调用一次,防止重复刷新token
     *
     * @return 是否刷新成功
     */
    private boolean refreshToken(String accessToken, ServletRequest request, ServletResponse response) {
        //加锁,保障同一token在同一时间只能刷新一次,避免重复刷新
        String lockKey = LockKeys.LOCK_REFRESH_TOKEN_PREFIX + accessToken;
        try {
            LockUtils.lock(lockKey);
            if (RedisUtils.hasKey(RedisKeys.AUTH_EXPIRING_TOKEN + accessToken)) {
                String newToken = (String) RedisUtils.get(RedisKeys.AUTH_EXPIRING_TOKEN + accessToken);
                // 提交给Realm进行认证
                try {
                    this.getSubject(request, response).login(new JWTToken(newToken));
                } catch (Exception ex) {
                    Console.log(ex.getMessage());
                    return false;
                }
                return true;
            } else {
                //获取用户id
                String subjectId = JWTUtils.getSubjectId(accessToken);
                String timestamp = JWTUtils.getTimestamp(accessToken);
                String jwtId = JWTUtils.getId(accessToken);
                boolean isAdmin = JWTUtils.isAdmin(accessToken);
                if (RedisUtils.hasKey(RedisKeys.AUTH_REFRESH_TOKEN + jwtId)) {
                    String currentTimeMillisRedis = (String) RedisUtils.get(RedisKeys.AUTH_REFRESH_TOKEN + jwtId);
                    // 获取当前AccessToken中的时间戳,与RefreshToken的时间戳对比,如果当前时间戳一致,进行AccessToken刷新
                    if (StringUtils.equals(currentTimeMillisRedis, timestamp)) {
                        // 生成新的jwt标识
                        String newJwtId = UUID.randomUUID().toString(true);
                        // 获取当前最新时间戳
                        String currentTimeMillis = String.valueOf(System.currentTimeMillis());
                        // 设置RefreshToken中的时间戳为当前最新时间戳并重置过期时间
                        RedisUtils.set(RedisKeys.AUTH_REFRESH_TOKEN + newJwtId, currentTimeMillis, refreshTokenExpireTime);
                        // 刷新AccessToken,设置时间戳为当前最新时间戳
                        String newAccessToken = JWTUtils.generate(newJwtId, subjectId, isAdmin, currentTimeMillis);
                        // 提交给Realm进行认证
                        try {
                            this.getSubject(request, response).login(new JWTToken(newAccessToken));
                        } catch (Exception ex) {
                            Console.log(ex.getMessage());
                            return false;
                        }
                        // 最后将刷新的AccessToken存放在Response的Header中的Authorization字段返回
                        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                        httpServletResponse.setHeader(ACCESS_TOKEN_KEY, newAccessToken);
                        // 设置旧token设置为即将过期,30秒
                        RedisUtils.set(RedisKeys.AUTH_EXPIRING_TOKEN + accessToken, newAccessToken, 30);
                        // 删除旧的refresh token
                        RedisUtils.del(RedisKeys.AUTH_REFRESH_TOKEN + jwtId);
                        return true;
                    }
                }
            }

        } finally {
            LockUtils.unlock(lockKey);
        }
        return false;
    }

    /**
     * 获取AccessToken
     *
     * @param request request
     * @return AccessToken
     */
    private String getAccessToken(ServletRequest request) {
        HttpServletRequest httpRequest = WebUtils.toHttp(request);
        String accessToken = httpRequest.getHeader(ACCESS_TOKEN_KEY);
        if (StringUtils.isBlank(accessToken)) {
            accessToken = httpRequest.getParameter(ACCESS_TOKEN_KEY);
        }
        return accessToken;
    }
}
JWTRealm
public class JWTRealm extends AuthorizingRealm {

    @Autowired
    @Lazy
    private AdministratorService administratorService;

    @Autowired
    @Lazy
    private UserSubjectService subjectService;

    @Autowired
    @Lazy
    private UserCredentialService credentialService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Object principal = principals.getPrimaryPrincipal();
        //数据库查询角色进行授权
        return new SimpleAuthorizationInfo(obtainRoles(principal));
    }

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String accessToken = (String) authenticationToken.getCredentials();
        //解析token
        String uid;
        String timestamp;
        String jwtId;
        boolean isAdmin;
        try {
            //验证jwt有效性
            JWTUtils.verify(accessToken);
            uid = JWTUtils.getSubjectId(accessToken);
            timestamp = JWTUtils.getTimestamp(accessToken);
            jwtId = JWTUtils.getId(accessToken);
            isAdmin = JWTUtils.isAdmin(accessToken);
        } catch (Exception ex) {
            throw new AuthenticationException("token is invalid.", ex);
        }
        //验证时间戳
        String currentTimeMillisRedis = (String) RedisUtils.get(RedisKeys.AUTH_REFRESH_TOKEN + jwtId);
        if (!StringUtils.equals(timestamp, currentTimeMillisRedis)) {
            throw new AuthenticationException("token timestamp incorrect.");
        }
        //运维管理员登录
        if (isAdmin) {
            AdministratorEntity admin = administratorService.getAdministrator(uid);
            if (admin == null) {
                throw new AuthenticationException("admin not found.");
            }
            //管理员context
            AdminContext acontext = AdminContext.builder()
                .accessToken(accessToken)
                .adminId(admin.getId())
                .name(admin.getName())
                .loginName(admin.getLoginName())
                .phone(admin.getPhone())
                .email(admin.getEmail())
                .build();
            acontext.setRoles(obtainRoles(acontext));
            return new SimpleAuthenticationInfo(acontext, accessToken, getName());
            //参数:1.主体 2. 凭证 3. realm名字
        }
    }

    /**
     * 获取角色
     *
     * @param principal 登录人
     * @return 角色列表
     */
    private Set<String> obtainRoles(Object principal) {
        Set<String> roles = new HashSet<>();
        //TODO 添加角色
        return roles;
}
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
好的,下面是一个基于Spring Boot前后端分离使用Token实现用户登录的代码示例: 后端代码: 1. 定义一个JWT工具类,用于生成和解析Token。 ```java @Component public class JwtUtils { // Token过期时间为1小时 private static final long EXPIRATION_TIME = 60 * 60 * 1000; // Token签名密钥 private static final String SECRET_KEY = "secret"; // Token前缀 private static final String TOKEN_PREFIX = "Bearer "; /** * 生成Token * * @param subject Token的主题,即用户ID * @return Token字符串 */ public static String generateToken(String subject) { Date now = new Date(); Date expiration = new Date(now.getTime() + EXPIRATION_TIME); String token = Jwts.builder() .setSubject(subject) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); return TOKEN_PREFIX + token; } /** * 解析Token * * @param token Token字符串 * @return Token主题,即用户ID */ public static String parseToken(String token) { if (token != null && token.startsWith(TOKEN_PREFIX)) { token = token.replace(TOKEN_PREFIX, ""); } try { Jws<Claims> claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token); return claims.getBody().getSubject(); } catch (JwtException e) { return null; } } } ``` 2. 在Controller中添加登录接口,用于验证用户登录,并生成Token。 ```java @RestController @RequestMapping("/api/user") public class UserController { @PostMapping("/login") public Result login(@RequestBody User user) { // 验证用户账号密码是否正确,这里省略具体实现 if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) { // 如果账号密码正确,则生成Token String token = JwtUtils.generateToken("1"); return Result.success(token); } else { return Result.error("账号或密码错误"); } } } ``` 3. 在Spring Boot的配置类中配置拦截器,用于验证Token是否有效。 ```java @Configuration public class WebConfig implements WebMvcConfigurer { // 添加拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new HandlerInterceptorAdapter() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从请求头中获取Token String token = request.getHeader("Authorization"); // 验证Token是否有效 String userId = JwtUtils.parseToken(token); if (userId != null) { request.setAttribute("userId", userId); return true; } else { // Token无效,返回401 Unauthorized response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } } }).addPathPatterns("/api/**"); // 拦截所有/api/**的请求 } } ``` 前端代码: 1. 在登录页面中,获取用户输入的账号密码,并发送登录请求。 ```html <form> <div class="form-group"> <label for="username">用户名:</label> <input type="text" class="form-control" id="username" name="username"> </div> <div class="form-group"> <label for="password">密码:</label> <input type="password" class="form-control" id="password" name="password"> </div> <button type="button" class="btn btn-primary" onclick="login()">登录</button> </form> ``` ```javascript function login() { var username = $("#username").val(); var password = $("#password").val(); $.ajax({ url: "/api/user/login", type: "POST", contentType: "application/json;charset=UTF-8", data: JSON.stringify({username: username, password: password}), success: function (result) { if (result.code == 200) { // 登录成功,将Token存入LocalStorage localStorage.setItem("token", result.data); window.location.href = "/index.html"; } else { // 登录失败,显示错误信息 alert(result.msg); } } }); } ``` 2. 在其他页面中,需要从LocalStorage中获取Token,并在请求头中添加Authorization字段。 ```javascript $(function () { var token = localStorage.getItem("token"); $.ajax({ url: "/api/user", type: "GET", headers: { "Authorization": token }, success: function (result) { if (result.code == 401) { // 用户未登录,跳转到登录页面 window.location.href = "/login.html"; } } }); }); ``` 以上就是一个基于Spring Boot前后端分离使用Token实现用户登录的代码示例,希望能对你有所帮助。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值