Token Auth
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
客户端使用用户名跟密码请求登录
服务端收到请求,去验证用户名与密码
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
Token Auth的优点
支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的
(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.
不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理. 基于标准化:你的API可以采用标准化的 JSON Web Token ( JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).
HRM中的TOKEN签发与验证
什么是JWT
SON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。在Java世界中通过JJWT实现JWT创建和验证。
JJWT的快速入门
token的创建
- 创建maven工程,引入依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
- 创建类CreateJwtTest,用于生成token
public class CreateJwtTest { public static void main(String[] args) { JwtBuilder builder= Jwts.builder().setId("888") .setSubject("小白") .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256,"itcast"); System.out.println( builder.compact() ); } }
- 测试运行,输出如下:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTM0NTh9.gq0J- cOM_qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk
token的解析
我们刚才已经创建了token,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。
创建ParseJwtTest
public class ParseJwtTest { public static void main(String[] args) { String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTM0NTh9.gq0J-cOM_qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk"; Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(token).getBody(); System.out.println("id:"+claims.getId()); System.out.println("subject:"+claims.getSubject()); System.out.println("IssuedAt:"+claims.getIssuedAt()); } }
试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token
自定义claims
我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims
- 创建CreateJwtTest3,并存储指定的内容
public class CreateJwtTest3 { public static void main(String[] args) { //为了方便测试,我们将过期时间设置为1分钟 long now = System.currentTimeMillis();//当前时间 long exp = now + 1000*60;//过期时间为1分钟 JwtBuilder builder= Jwts.builder().setId("888") .setSubject("小白") .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256,"itcast") .setExpiration(new Date(exp)) .claim("roles","admin") //自定义claims存储数据 .claim("logo","logo.png"); System.out.println( builder.compact() ); } }
- 修改ParseJwtTest,获取指定信息
public class ParseJwtTest { public static void main(String[] args) { String compactJws="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MT czMjMsImV4cCI6MTUyMzQxNzM4Mywicm9sZXMiOiJhZG1pbiIsImxvZ28iOiJsb2dvLnBuZyJ9.b11p4g4rE94r qFhcfzdJTPCORikqP_1zJ1MP8KihYTQ"; Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJws).getBody(); System.out.println("id:"+claims.getId()); System.out.println("subject:"+claims.getSubject()); System.out.println("roles:"+claims.get("roles")); System.out.println("logo:"+claims.get("logo")); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); System.out.println("签发时间:"+sdf.format(claims.getIssuedAt())); System.out.println("过期时间:"+sdf.format(claims.getExpiration())); System.out.println("当前时间:"+sdf.format(new Date()) ); } }
JWT工具类
在ihrm_common工程中创建JwtUtil工具类
package com.ihrm.common.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Date; import java.util.Map; @Getter @Setter @ConfigurationProperties("jwt.config") public class JwtUtils { //签名私钥 private String key; //签名的失效时间 private Long ttl; /** * 设置认证token * id:登录用户id * subject:登录用户名 * */ public String createJwt(String id, String name, Map<String,Object> map) { //1.设置失效时间 long now = System.currentTimeMillis();//当前毫秒 long exp = now + ttl; //2.创建jwtBuilder JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(name) .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, key); //3.根据map设置claims for(Map.Entry<String,Object> entry : map.entrySet()) { jwtBuilder.claim(entry.getKey(),entry.getValue()); } jwtBuilder.setExpiration(new Date(exp)); //4.创建token String token = jwtBuilder.compact(); return token; } /** * 解析token字符串获取clamis */ public Claims parseJwt(String token) { Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); return claims; } }
- 修改ihrm_common工程的application.yml, 添加配置
jwt: config: key: saas-ihrm ttl: 360000
登录成功签发token
- 配置JwtUtil。修改ihrm_system工程的启动类
@Bean public JwtUtil jwtUtil(){ return new util.JwtUtil(); }
- UserController.java添加登录方法
/** * 用户登录 * 1.通过service根据mobile查询用户 * 2.比较password * 3.生成jwt信息 * */ @RequestMapping(value="/login",method = RequestMethod.POST) public Result login(@RequestBody Map<String,String> loginMap) { String mobile = loginMap.get("mobile"); String password = loginMap.get("password"); User user = userService.findByMobile(mobile); //登录失败 if(user == null || !user.getPassword().equals(password)) { return new Result(ResultCode.MOBILEORPASSWORDERROR); }else { //登录成功 Map<String,Object> map = new HashMap<>(); map.put("companyId",user.getCompanyId()); map.put("companyName",user.getCompanyName()); String token = jwtUtils.createJwt(user.getId(), user.getUsername(), map); return new Result(ResultCode.SUCCESS,token); } }
- 测试运行结果
使用postman验证登录返回:
{"success":true,"code":10000,"message":"操作成 功!","data":"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDYyNjYxODkxNjE4Mzc3NzI4Iiwic3ViIjoiemhhb mdzYW4iLCJpYXQiOjE1NDI0NjgzNzcsImNvbXBhbnlJZCI6IjEiLCJjb21wYW55TmFtZSI6IuS8oOaZuuaSreWu oiIsImV4cCI6MTU0MjU1NDc3N30.J-8uv8jOp2GMLpBwrUOksnErjA4-DOJ_qvy7tsJbsa8"}
获取用户信息鉴权
需求:用户登录成功之后,会发送一个新的请求到服务端,获取用户的详细信息。获取用户信息的过程中必须登录才能,否则不能获取。
前后端约定:前端请求微服务时需要添加头信息Authorization ,内容为Bearer+空格+token
- 添加响应值对象ProfileResult
package com.ihrm.domain.system.response; import com.ihrm.domain.system.Permission; import com.ihrm.domain.system.Role; import com.ihrm.domain.system.User; import lombok.Getter; import lombok.Setter; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @Setter @Getter public class ProfileResult { private String mobile; private String username; private String company; private Map<String,Object> roles = new HashMap<>(); public ProfileResult(User user) { this.mobile = user.getMobile(); this.username = user.getUsername(); this.company = user.getCompanyName(); Set<Role> roles = user.getRoles(); Set<String> menus = new HashSet<>(); Set<String> points = new HashSet<>(); Set<String> apis = new HashSet<>(); for (Role role : roles) { Set<Permission> perms = role.getPermissions(); for (Permission perm : perms) { String code = perm.getCode(); if(perm.getType() == 1) { menus.add(code); }else if(perm.getType() == 2) { points.add(code); }else { apis.add(code); } } } this.roles.put("menus",menus); this.roles.put("points",points); this.roles.put("apis",apis); } }
- UserController.java添加profile方法
/** * 获取个人信息 */ @RequestMapping(value = "/profile", method = RequestMethod.POST) public Result profile(HttpServletRequest request) throws Exception { //临时使用 String userId = "1"; User user = userService.findById(userId); return new Result(ResultCode.SUCCESS,new ProfileResult(user)); }
- 验证token
思路:从请求中获取key为Authorization的token信息,并使用jwt验证,验证成功后获取隐藏信息。 修改profile方法添加如下代码
@RequestMapping(value = "/profile", method = RequestMethod.POST) public Result profile(HttpServletRequest request) throws Exception { //请求中获取key为Authorization的头信息 String authorization = request.getHeader("Authorization"); if(StringUtils.isEmpty(authorization)) { throw new CommonException(ResultCode.UNAUTHENTICATED); } //前后端约定头信息内容以 Bearer+空格+token 形式组成 String token = authorization.replace("Bearer ", ""); //比较并获取claims Claims claims = jwtUtil.parseJWT(token); if(claims == null) { throw new CommonException(ResultCode.UNAUTHENTICATED); } String userId = claims.getId(); User user = userService.findById(userId); return new Result(ResultCode.SUCCESS,new ProfileResult(user)); }