目录
码字不易,喜欢就点个关注❤,持续更新技术内容。相关资料请私信。
相关内容:
第一篇:原生JS到Vue前端工程化开发_js转vue代码-CSDN博客
第一篇:SpringBoot项目的创建和开发_Maxlec的博客-CSDN博客
1 常见的用户认证方案
互联网服务离不开用户认证。常见的用户认证鉴权方式如下:
-
Cookie Session
-
JWT,Token 认证
-
OAuth2 认证
-
SSO 单点登录
-
扫码认证
2 Session认证
2.1 早期Session认证
早期互联网以 web 为主,客户端是浏览器,所以 Cookie-Session 方式最那时候最常用的方式,直到现在,一些 web 网站依然用这种方式做认证。
Session认证一般流程是:
-
用户向服务器发送用户账户和密码
-
服务器验证通过后,在当前对话(Session)看里面保存相关数据,比如用户角色、登录时间等
-
服务向用户返回一个session_id,写入用户的Cookie
-
用户随后的每一次请求,都会通过Cookie,将session_id传回服务器
-
服务器收到session_id,找到前期保存的数据,由此得知用户的身份
由于传统的 Cookie-Session 认证存在诸多问题,可以把上面的方案改造一下。改动的地方如下:
-
不用 cookie 做客户端存储,改用其他方式,web 下使用 local storage,APP 中使用客户端数据库,这样就实现了跨域,并且避免了 CSRF ;
-
服务端也不存 Session 了,把 Session 信息拿出来存到 Redis 等内存数据库中,这样即提高了速度,又避免了 Session 同步问题;
2.2 改进的Session认证
经过改造之后变成了如下的认证过程:
-
用户输入用户名、密码或者用短信验证码方式登录系统;
-
服务端经过验证,将认证信息构造好的数据结构存储到 Redis 中,并将 key 值返回给客户端;
-
客户端拿到返回的 key,存储到 local storage 或本地数据库;
-
下次客户端再次请求,把 key 值附加到 header 或者 请求体中;
-
服务端根据获取的 key,到 Redis 中获取认证信息;
session认证的方式应用非常普遍,但也存在一些问题,扩展性不好。如果是服务器集群部署,或者是跨域的服务导向架构,就要求session数据共享,每台服务器都能够读取session,针对这种问题有两种解决方案:
-
session数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。
-
一种是服务器不再保存session数据,所有数据都保存在客户端,每次请求都发回服务器。Token认真就是这种方案的一个代表。
3 JwtToken认证
3.1 Token介绍
Token是在服务端产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证,token认证的一般流程如下:
-
用户在客户端提交账户和密码表单请求登录,服务端接收到表单验证账户和密码,验证成功,服务端生成一个token响应返回给客户端,客户端接收并存储在cookie中或者localstorage中。
-
客户端每次向服务端请求资源时都需要带上服务端签发的token,服务端接收到请求,验证请求中的token是否合法,验证成功就允许客户端访问,响应数据。
token解析的特点:
-
服务器不需要保存任何的session或者cookie。,基于token的用户认证是一种由服务端无状态的事件驱动的认证方式。
-
解析token的计算时间换取了session的存储空间,从而减轻服务器的压力,减少频繁的数据库查询。
-
token认证完全由应用管理,所以token可以避开同源策略。
3.2 JwtToken
JSON Web Token(JWT)是一个非常轻巧的规范,是目前最流行的跨域认证解决方案。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
Jwt的原理是就是Token认证,在服务认证以后,生成一个JSON对象,返回给客户端用户。用户与服务器通信时,都要带上这个JSON对象。服务器完全靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
特点:
-
客户端收到服务器返回的JWT字符串后可以存储在Cookie中,也可以存储在localstorage中。
-
客户端每次与服务器通信,都要带上这个JWT字符串,可以把它放在Cookie中自动发送,但这样不能跨域,更好的做法是放在HTTP请求的头信息"Authorization"字段里面,单独发送。
优点
-
使用 json 作为数据传输,有广泛的通用型,并且体积小,便于传输;
-
不需要在服务器端保存相关信息;
-
jwt 载荷部分可以存储业务相关的信息(非敏感的),例如用户信息、角色等;
JWT由三个部分组成:Header、Payload、Signature,三部分最终组合为完整的字符串,中间用"."隔开:Header.Payload.Signature。
3.2.1 Header头部
Header部分是一个JSON对象,描述JWT的元数据:
{
"alg": "HS256",
"typ": "JWT"
}
alg属性是签名的算法,默认是HS256》typ属性表示这个令牌(token)的类型(type),JWT令牌统一写为JWT。最后将JSON使用Base64URL算法转换成字符串。
3.2.2 Payload载荷
Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,如签发人、生效时间、过期时间、编号等等。这个JSON对象也使用Base64URL算法转换成字符串。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
3.2.3 Signature签名
Signature部分是对前两部分的签名,防止数据被篡改。首先需要指定一个密钥(secret)。这个密钥只有服务器知道,不能泄露给用户。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
abcdefabcdefabcdef
)
算出签名以后,把Header、Payload、Signature三个部分拼成一个字符串,每个部分之间使用"."分隔,返回给客户端。
4 SpringBoot集成实现Jwt认证
4.1 实现
创建SpringBoot项目,写好相关API后,集成Jwt的首先一步是引入Jwt的依赖坐标:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
创建Jwt工具类,在类中定义生成JwtToken的方法,以及解析token的方法:
public class JwtUtils {
//32个字符密钥
private static final String secret = "abcdefghiabcdefghiabcdefghi";
//生成token
public static String generateToken(User user){
//七天过期
long expire = 604800;
Date expiration = new Date(System.currentTimeMillis() + 1000*expire);
// 设置载荷
Map<String, Object> claims = new HashMap<>();
claims.put("uname",user.getUname());
claims.put("pwd", user.getPwd()); // 一般不以密码为载荷,因为token可能被解析
// 返回JwtToken字符串
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256,secret) // 签名算法
.setClaims(claims)
.setExpiration(expiration)
.compact();
}
//解析token
public static Claims getClaimsByToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}
然后在业务层中定义处理方法,验证用户传递过来的用户信息是否正确,通过后返回包含token的结果集:
@Service
public class UserService {
@Autowired
UserMapper userMapper;
// 传入账户和密码,生成token
public TokenResult GetLoginToken(User user)
{
List<User> list = userMapper.queryUserByName(user.getUsername());
TokenResult result = new TokenResult();
if (list.size() > 0)
{
if (Objects.equals(list.get(0).getPassword(), user.getPassword()))
{
String token = JwtUtils.generateToken(user);
Map<String, Object> data = new HashMap<>();
data.put("token", token);
result.setCode(20000);
result.setSuccess(true);
result.setMessage("成功");
result.setData(data);
}
else
{
result.setCode(0);
result.setSuccess(false);
result.setMessage("密码错误");
}
}
else
{
result.setCode(0);
result.setSuccess(false);
result.setMessage("账号错误");
}
return result;
}
}
接下来就可以在控制器接口方法中直接使用,当用户输入账户和密码点击登录调用远程调用以下接口,并传递账户和密码的表单,后端就可以进行账户和密码的验证,通过后就会生成返回一个token:
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {
@Autowired
private UserService userService;
@ApiOperation("接收登录表单生成token")
@PostMapping("/login")
//前端一般传递的数据是json格式,必须使用对象接收,同时需要添加@RequestBody注解
public TokenResult login(@RequestBody User user){
// 传入用户包含账户和密码的对象,在验证通过后返回包含token的结果集
TokenResult tokenResult = userService.GetLoginToken(user);
//返回包含token的结果集
return tokenResult;
}
}
4.2 测试
首先打开我已经配置好Swagger调试页面进行调试,相关配置可以看我另外一篇SpringBoot的文章。如下图成功返回包含JwtToken的结果集:
现在打开启动前端项目,相关的内容可以看Vue的文章,启动项目进入登录页面输入账户和密码:
成功响应并跳转成功: