Java实现基于token认证
Java实现基于token认证
随着互联网的不断发展,技术的迭代也非常之快。我们的用户认证也从刚开始的用户名密码转变到基于cookie的session认证,然而到了今天,这种认证已经不能满足与我们的业务需求了(分布式,微服务)。我们采用了另外一种认证方式:基于token的认证。
一、与cookie相比较的优势
- 支持跨域访问,将token置于请求头中,而cookie是不支持跨域访问的
- 无状态化,服务端无需存储token,只需要验证token信息是否正确即可,而session需要在服务端存储,一般是通过cookie中的sessionID在服务端查找对应的session
- 无需绑定到一个特殊的身份验证方案(传统的用户名密码登陆),只需要生成的token是符合我们预期设定的即可
- 更适用于移动端(Android,iOS,小程序等等),像这种原生平台不支持cookie,比如说微信小程序,每一次请求都是一次会话,当然我们可以每次去手动为他添加cookie
- 避免CSRF跨站伪造攻击,还是因为不依赖cookie
- 常适用于RESTful API,这样可以轻易与各种后端(java,.net,python…)相结合,去耦合
还有一些优势这里就不一一列举了。
二、基于JWT的token认证实现
JWT:JSON Web Token,其实token就是一段字符串,由三部分组成:Header,Payload,Signature。详细情况请自行百度,现在,上代码。
-
引入依赖,这里选用java-jwt,选择其他的依赖也可以
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.3.0</version> </dependency>
-
实现签名方法
/** * 生成签名,15min后过期 * * @param username 用户名 * @return 加密的token */ public static String sign(String username, String userId) { try { // 过期时间 Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); // 私钥及加密算法 Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); // 设置头部信息 Map<String, Object> header = new HashMap<>(2); header.put("typ", "JWT"); header.put("alg", "HS256"); // 附带username,userId信息,生成签名 return JWT.create().withHeader(header).withClaim("loginName", username).withClaim("userId", userId) .withExpiresAt(date).sign(algorithm); } catch (UnsupportedEncodingException e) { return null; } }
/** * 过期时间一天, TODO 正式运行时修改为15分钟 */ private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; /** * token私钥 */ private static final String TOKEN_SECRET = "f26e587c28064d0e855e72c0a6a0e618";
设置15分钟过期也是出于安全考虑,防止token被窃取,不过一般选择基于token认证,传输方式我们都应该选择https,这样别人无法抓取到我们的请求信息。这个私钥是非常重要的,加密解密都需要用到它,要设置的足够复杂并且不被盗取,我这里用的是一串uuid,加密方式HMAC256.
-
认证
package com.joe.web; import com.joe.entity.ApiResponse; import com.joe.entity.User; import com.joe.enums.ApiResponseEnum; import com.joe.service.IUserService; import com.joe.util.ApiResponseUtil; import com.joe.util.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import java.util.Map; /** * @author:joe * @date:2019/5/20 19:40 */ @Controller @RequestMapping("/") public class LoginController { @Autowired private IUserService userService; /** * 登陆接口 * * @return token */ @RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public ApiResponse login(@RequestBody Map<String, String> map) { String loginName = map.get("loginName"); String password = map.get("password"); //身份验证是否成功 boolean isSuccess = userService.checkUser(loginName, password); if (isSuccess) { User user = userService.getUserByLoginName(loginName); if (user != null) { //返回token String token = JwtUtil.sign(user.getName(), user.getId()); if (token != null) { return ApiResponseUtil.getApiResponse(token); } } } //返回登陆失败消息 return ApiResponseUtil.getApiResponse(ApiResponseEnum.LOGIN_FAIL); } }
-
配置拦截器
package com.joe.interceptor; import com.alibaba.fastjson.JSONObject; import com.joe.entity.ApiResponse; import com.joe.enums.ApiResponseEnum; import com.joe.util.ApiResponseUtil; import com.joe.util.JwtUtil; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** * 自定义token拦截器 * * @author qiaokun * @date 2018/08/11 */ public class TokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { response.setCharacterEncoding("utf-8"); String token = request.getHeader("access_token"); //token不存在 if (null != token) { //验证token是否正确 boolean result = JwtUtil.verify(token); if (result) { return true; } } ApiResponse apiResponse = ApiResponseUtil.getApiResponse(ApiResponseEnum.AUTH_ERROR); responseMessage(response,response.getWriter(),apiResponse); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } /** * 返回信息给客户端 * * @param response * @param out * @param apiResponse */ private void responseMessage(HttpServletResponse response, PrintWriter out, ApiResponse apiResponse) { response.setContentType("application/json; charset=utf-8"); out.print(JSONObject.toJSONString(apiResponse)); out.flush(); out.close(); } }
实现HandleInterceptor,重写preHandle方法,该方法是在每个请求之前触发执行,从request的头里面取出token,这里我们统一了存放token的键为accessToken,验证通过,放行,验证不通过,返回认证失败信息。
-
设置拦截器
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <mvc:exclude-mapping path="/login/"/> <bean class="com.joe.interceptor.TokenInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
这里使用的是Spring的xml配置拦截器,放过认证接口。
-
token解码方法
/** * 校验token是否正确 * * @param token 密钥 * @return 是否正确 */ public static boolean verify(String token) { try { Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); return true; } catch (Exception exception) { return false; } }
-
测试
登录
接口:localhost:8081/user
post=>{ //body "loginName":"zsj", "password":"123456" }
{ "errCode": 10000, "errMsg": "请求成功", "data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbk5hbWUiOiJqb2UiLCJleHAiOjE1NjE4ODYwODAsInVzZXJJZCI6IjIwMTkifQ.obShKP4jJjmpadxAP0KuAwHgdcjfgu_mACQbUNmNm1A" }
-
查询用户
get=>{ //header "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbk5hbWUiOiJqb2UiLCJleHAiOjE1NjE4ODU1MzAsInVzZXJJZCI6IjIwMTkifQ.nNR2z_nY4_3ATllZqK21uUPcvYcCauWWdDiyV0xFA0A" }
{ "errCode": 10000, "errMsg": "请求成功", "data": { "id": "2019", "name": "joe", "age": 18 } }
获取token里携带的信息
我们可以将一些常用的信息放入token中,比如用户登陆信息,可以方便我们的使用
/** * 获得token中的信息无需secret解密也能获得 * * @return token中包含的用户名 */ public static String getUsername(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("loginName").asString(); } catch (JWTDecodeException e) { return null; } }
代码的侵入性
当你的代码引入了一个组件,导致其它代码或者设计,要做相应的更改以适应新组件。这样的情况我们就认为这个新组件具有侵入性。同时,这里又涉及到一个设计方面的概念,就是耦合性的问题。我们代码设计的思路是"高内聚,低耦合",为了实现这个思路,就必须降低代码的侵入性.
- Java实现基于token认证
- Java实现基于token认证
- 使用 AngularJS & NodeJS 实现基于 token 的认证
- Java开发之spring security实现基于MongoDB的认证功能
- 使用 AngularJS & NodeJS 实现基于token 的认证应用(转)
- ASP.NET WebApi 基于分布式Session方式实现Token签名认证(发布版)
- Axis2+Rampart(WSS4J)实现UsernameToken认证方式的WS-Security(基于SOAP的Web安全调用机制)
- 基于SpringMVC实现登录认证的过程----subject.login(token)
- Laravel 5 中使用 JWT(Json Web Token) 实现基于API的用户认证
- JWT基于filter实现token的认证
- 基于python3的token认证和鉴权不用解密的实现
- ASP.NET WebApi 基于JWT实现Token签名认证(发布版)
- 基于SOAP的Web安全调用机制-----Axis2+Rampart(WSS4J)实现UsernameToken认证方式的WS-Security【未试验】
- 使用 AngularJS & NodeJS 实现基于 token 的认证应用
- NodeJS 实现基于 token 的认证应用
- NodeJS 实现基于 token 的认证应用
- 使用 AngularJS & NodeJS 实现基于 token 的认证应用
- Java登录认证-基于userId+token-框架flylib-passport
- 使用 AngularJS & NodeJS 实现基于 token 的认证应用
- 使用 AngularJS & NodeJS 实现基于 token 的认证应用