Java实现登录功能(一)



前言

刚开始学习Java的时候,你是否有过疑问,登录功能就是简单地查询数据库对比一下账号密码就行了吗?在进行权限认证的时候,又该怎么办呢?每次都在提交的表单上加上账号密码吗?在这种情况下又该怎么防止CSRF(跨站请求伪造)呢?
以下介绍一种现在流行的基于JWT的Token认证实现的登录功能。


一、Token的认证流程及优点

认证流程:

1.客户端发送带有用户名和密码参数的登录请求
2.服务端收到请求,验证用户名和密码
3.验证通过后,服务端签发Token并返回token给客户端
4.客户端收到Token后存储到Cookie或者Storage等中
5.客户端每次向服务端请求资源时需要在cookie或者header请求头中携带服务端签发的token
6.服务端收到请求后验证客户端请求携带的token,如果验证通过就向客户端返回请求数据
在这里插入图片描述

一般情况下,Token是被Header请求头所携带的。
有着以下优点:

1.支持跨域:Cookie是无法跨域的,而Token可以放在header请求头发送,所以支持跨域。
2.无状态:Token认证方式在服务端不需要像传统的Session认证方式那样去记录SessionId,因为Token本身包含了登录用户的信息,可以减轻服务端的压力。
3.支持移动端、小程序:当客户端是非浏览器平台时,Cookie是不被支持的,此时采用在Header请求头携带Token的认证方式会显得简单
4.能避免CSRF失效:由于CSRF依赖于Cookie,所以由Header请求头携带Token的认证方式不会发生CSRF,所以能避免CSRF

说明

CSRF——跨站请求伪造。其原理是通过伪造成其它内容的请求地址,诱导用户触发请求。如果用户在登录状态下触发请求,将会自动携带Cookie访问服务端,服务端接收请求后检查到Cookie内部的用户信息会认为是登录用户本人的操作。

二、JWT

JWT(JSON Web Token)是一种比较主流的使用规范。其结构分别由Header标头、Payload有效负载、Signature签名算法三部分组成。

1.Header负责记录令牌类型、签名算法等。
such as: {"alg":"HS256","type","JWT"}

2.Payload负责声明一些标准以及携带一些用户信息
{"userId":"1","username":"mayikt"}
标准声明:
	iss:JWT签发者
	sub:JWT所面向的用户
	aud:JWT的接收者
	exp:JWT的过期时间,过期时间必须大于签发时间
	nbf:JWT的生效时间
	iat:JWT的签发时间
	jti:JWT的唯一身份标识,避免重复
自定义的声明:
	一些不敏感的用户信息。
such as: {"iss":"CJJ","sub":"custom","exp":"1640000000","iat":"1632000000","jti":"156sad54a615a4d56ad15a6sd15s8924","userId":"1"}

3.Signature防止Token被篡改,加盐,确保安全

三、Java基于JWT实现登录功能

1.引入pom依赖

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.18.3</version>
        </dependency>

2.编写JWT工具类

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.demo.domain.User;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtil {
    //token有效时长
    private static final long EXPIRE=30*60*1000L;
    //token的密钥 可自行定义
    private static final String SECRET="jwt";


    public static String createToken(User user) throws UnsupportedEncodingException {
        //token过期时间
        Date date=new Date(System.currentTimeMillis()+EXPIRE);

        //jwt的header部分
        Map<String ,Object>map=new HashMap<>();
        map.put("alg","HS256");
        map.put("typ","JWT");

        //使用jwt的api生成token
        String token= JWT.create()
                .withHeader(map)
                .withClaim("username", user.getUsername())//私有声明
                .withExpiresAt(date)//过期时间
                .withIssuedAt(new Date())//签发时间
                .sign(Algorithm.HMAC256(SECRET));//签名
        return token;
    }

    /**
     * 校验token的有效性
     * 1 token的header和payload是否没改过
     * 2 没有过期
     */
    public static boolean verify(String token){
        try {
            //解密
            JWTVerifier verifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            verifier.verify(token);
            return true;
        }catch (Exception e){
            return false;
        }
    }
    //无需解密也可以获取token的信息
    public static String getUsername(String token){
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }
}

3.编写登录接口

domain、mapper、service层请自行编写,其中Result类是统一返回结果集类

import com.example.demo.domain.User;
import com.example.demo.service.UserService;
import com.example.demo.utils.JWTUtil;
import com.example.demo.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.UnsupportedEncodingException;

@RestController
public class LoginController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result login(@RequestParam String username, @RequestParam String password) throws UnsupportedEncodingException {
        User user=userService.getUserByPass(username, password);
        if(user == null){
        	return Result.fail("用户名或密码错误");
        }
        //登录成功则调用JWTUtil类的创建Token方法返回客户端
        String token= JWTUtil.createToken(user);
        return Result.succ(200,"登陆成功",token);
    }
    
}

4.Java后端校验Token测试

import com.example.demo.utils.Result;
import com.example.demo.utils.JWTUtil;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/test")
    public Result test(HttpServletRequest request){
    	String token=req.getHeader("Authorization");
    	if(token == null){
    		return Result.fail("尚未登录");
    	}
    	//获取到token中的用户信息
    	System.out.println(JWTUtil.getUsername(token));
    	//可自行编写获取用户信息后的操作
    	//......
    	
        return Result.succ("test");
    }
    
}

此处仅进行测试,后续博客将介绍安全框架搭配使用。

5.Vue登录页面中的存储Token方法

methods: {
    submitLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.loading = true
          this.postRequest('/login', this.loginForm, 'form').then((resp) => {
            if (resp) {
              // 存储用户token
              const tokenStr = resp.obj.tokenHead + resp.obj.token
              window.sessionStorage.setItem('tokenStr', tokenStr)

              let path = this.$route.query.redirect
              this.$router.replace(
                path == '/' || path == undefined ? '/home' : path
              )
            }
          })
        } else {
          this.$message.error('你还有未填写的信息')
          return false
        }
      })
    },
  },

6.Vue前端每次请求资源携带Token方法

import axios from 'axios'
// 请求拦截器
axios.interceptors.request.use(config => {
        if (window.sessionStorage.getItem('tokenStr')) {
            //请求携带自定义token
            config.headers['Authorization'] =
                window.sessionStorage.getItem('tokenStr');
        }
        return config
    }, error => {
        console.log(error);
    })

温馨提示:
遇到报错跨域问题可参考 解决跨域问题


总结

本文简单地介绍了基于JWT的Token认证方式实现的登录功能。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐