如何使用token进行身份认证
什么是token?
token是一种用于身份认证的技术,它是一个包含用户信息和签名的字符串,通常由服务器生成并返回给客户端。客户端在每次请求时,都需要携带token来证明自己的身份。服务器通过验证token的有效性和完整性,来授权客户端访问受保护的资源。
为什么要使用token?
使用token进行身份认证有以下几个优点:
- 无状态:token不需要服务器存储或维护,可以减轻服务器的负担,提高性能和可扩展性。
- 安全:token可以防止重放攻击,因为每个token都有一个过期时间,过期后就无法使用。token也可以防止篡改攻击,因为每个token都有一个签名,如果被修改,签名就会失效。
- 灵活:token可以支持多种认证方式,例如用户名密码、第三方登录、短信验证码等。token也可以支持多种授权方式,例如基于角色、基于权限、基于范围等。
如何设计token?
一个典型的token由三部分组成:头部(header)、载荷(payload)和签名(signature)。它们之间用点号(.)分隔,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNjE0NzUwNzQ3LCJyb2xlIjoiYWRtaW4ifQ.7y8nZbLh8q1x6rZy7kKlHwv5fXs2fLqk8vVQfYm4c0o
- 头部(header):包含了token的类型(typ)和使用的算法(alg),通常使用JSON格式,并进行Base64编码。
- 载荷(payload):包含了用户的信息(sub, name)和其他的声明(exp, role),通常使用JSON格式,并进行Base64编码。
- 签名(signature):由头部和载荷通过算法(alg)和密钥(secret)生成的散列值,用于验证token的完整性。
如何实现token?
在JAVA语言中,可以使用一些开源的库来实现token的生成和验证,例如jjwt, nimbus-jose-jwt等。以下是一个基于token身份认证的完整实例,它使用了Java语言和Redis数据库。它的主要步骤如下:
- 在web.xml中配置需要认证的请求,例如
/**/userApi/**/**
。 - 实现一个过滤器类(AuthFilter),用于拦截需要认证的请求,并调用一个认证类(Author)来验证token。
- 实现一个认证类(Author),用于从请求头或参数中获取token,并从Redis中获取对应的用户ID。如果用户ID存在,说明token有效,将用户ID存入Redis并放行请求;如果用户ID不存在,说明token无效或过期,拒绝请求并返回错误信息。
- 实现一个登录接口(LoginController),用于接收用户的用户名和密码,并验证用户的合法性。如果用户合法,生成一个token,并将token和用户ID存入Redis,并返回token给客户端;如果用户不合法,返回错误信息给客户端。
- 实现一个获取用户信息的接口(UserController),用于根据token从Redis中获取用户ID,并根据用户ID从数据库中获取用户信息,并返回给客户端。
以下是JAVA实例的代码:
// 导入相关的类
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
// 生成一个密钥
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 过滤器类
public class AuthFilter implements Filter {
protected Pattern[] ignorePattern = null;
PathMatcher matcher = new AntPathMatcher();
// 不用过滤的请求
String[] ignoreStrs = null;
// 需要过滤的请求
String[] noIgnoreStrs = null;
public AuthFilter() {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String uri = request.getRequestURI();
// 判断是否需要认证
if (this.checkIgnore(uri) && !this.checkNoIgnore(uri)) {
chain.doFilter(request, response);
} else {
// 用户认证
Author.checkAuthorLocal(request, response, chain);
}
}
@Override
public void init(FilterConfig config) throws ServletException {
String ignore = config.getInitParameter("ignore");
String noIgnore = config.getInitParameter("noIgnore");
ignore = StringUtils.defaultIfEmpty(ignore, "");
this.ignoreStrs = ignore.replaceAll("\\s", "").split(",");
this.noIgnoreStrs = noIgnore.replaceAll("\\s", "").split(",");
}
public boolean checkIgnore(String requestUrl) {
boolean flag = false;
String[] var6 = this.ignoreStrs;
int var5 = this.ignoreStrs.length;
for(int var4 = 0; var4 < var5; ++var4) {
String pattern = var6[var4];
if (flag = this.matcher.match(pattern, requestUrl)) {
break;
}
}
return flag;
}
public boolean checkNoIgnore(String requestUrl) {
boolean flag = false;
String[] var6 = this.noIgnoreStrs;
int var5 = this.noIgnoreStrs.length;
for(int var4 = 0; var4 < var5; ++var4) {
String pattern = var6[var4];
if (flag = this.matcher.match(pattern, requestUrl)) {
break;
}
}
return flag;
}
}
// 认证类
public class Author {
public Author() {
}
public static void checkAuthorLocal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!request.getMethod().equals("OPTIONS")) {
// 获取token
String token = request.getHeader("oauth_token");
if (!StringUtil.hasText(token)) {
token = request.getParameter("oauth_token");
}
String loginUserId;
if (StringUtil.hasText(token)) {
// 判断token有效性
loginUserId = getLoginUserId(token);
if (StringUtil.hasText(loginUserId)) {
setLoginUserId(token, loginUserId);
chain.doFilter(request, response);
} else {
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
PrintWriter writer = response.getWriter();
writer.print("no access");
return;
}
} else {
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
PrintWriter writer = response.getWriter();
writer.print("no access");
return;
}
}
}
private static String getLoginUserId(String accessToken) {
String userId = RedisClient.get(accessToken);
return userId;
}
public static void setLoginUserId(String accessToken, String userId) {
try {
RedisClient.set(accessToken, userId, Cluster.getTimeoutSecond());
} catch (Exception var3) {
var3.printStackTrace();
}
}
}
// 登录接口
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody User user) {
// 验证用户合法性
User dbUser = userService.findByUsernameAndPassword(user.getUsername(), user.getPassword());
if (dbUser != null) {
// 生成token
String token = Jwts.builder()
.setSubject(dbUser.getId()) // 设置用户ID
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 设置过期时间为1小时
.signWith(key) // 使用密钥签名
.compact(); // 生成字符串
// 将token和用户ID存入Redis
Author.setLoginUserId(token, dbUser.getId());
// 返回token给客户端
return Result.success(token);
} else {
// 返回错误信息给客户端
return Result.fail("用户名或密码错误");
}
}
}
// 获取用户信息的接口
@RestController
@RequestMapping("/userApi")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/getUserInfo")
public Result getUserInfo(HttpServletRequest request) {
// 根据token从Redis中获取用户ID
String token = request.getHeader("oauth_token");
String userId = Author.getLoginUserId(token);
// 根据用户ID从数据库中获取用户信息
User user = userService.findById(userId);
// 返回用户信息给客户端
return Result.success(user);
}
}
总结
使用token进行身份认证是一种流行的技术,它有无状态、安全和灵活的特点。token由头部、载荷和签名组成,可以使用一些开源的库来实现token的生成和验证。本文介绍了一个基于token身份认证的完整实例,它使用了Java语言和Redis数据库。希望这篇博客对你有所帮助。