博学谷-基于SaaS平台的iHRM实战开发 SaaS权限管理与jwt鉴权第六天
第4章 权限管理与jwt鉴权
最后附上视频下载地址
学习目标:
理解权限管理的需求以及设计思路
实现角色分配和权限分配
理解常见的认证机制
能够使用JWT完成微服务Token签发与验证
4 常见的认证机制
4.1 HTTP Basic Auth
HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合
RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的
风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth
4.2 Cookie Auth
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie
对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏
览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效
4.3 OAuth
OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资
源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。 OAuth允许用户提供一个令牌,而
不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频
编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,
OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用。
4.4 Token Auth
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
1. 客户端使用用户名跟密码请求登录
2. 服务端收到请求,去验证用户名与密码
3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
Token Auth的优点
1)支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
2)无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
3)更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
4)去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
5)更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
6)CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.
7)不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
8)基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby,Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).
5 HRM中的TOKEN签发与验证
5.1 什么是JWT
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的
信息。在Java世界中通过JJWT实现JWT创建和验证。
5.2 JJWT的快速入门
5.2.1 token的创建
(1)创建maven工程,引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
(2)创建类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() );
}
}
(3)测试运行,输出如下:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTM0NTh9.gq0JcOM_
qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk
5.2.2 token的解析
我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送
请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息
(例如用户id),根据这些信息查询数据库返回相应的结果。
创建ParseJwtTest
public class ParseJwtTest {
public static void main(String[] args) {
String
token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiO
jE1MjM0MTM0NTh9.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
5.2.3 自定义claims
我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims
(1) 创建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() );
}
}
(2) 修改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()) );
}
}
5.3 JWT工具类
在ihrm_common工程中创建JwtUtil工具类
@ConfigurationProperties("jwt.config")
public class JwtUtil {
private String key;
private long ttl;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
/**
* 签发 token
*/
public String createJWT(String id, String subject,Map<String,Object> map){
long now=System.currentTimeMillis();
long exp=now+ttl;
JwtBuilder jwtBuilder = Jwts.builder().setId(id)
.setSubject(subject).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, key);
for(Map.Entry<String,Object> entry:map.entrySet()) {
jwtBuilder.claim(entry.getKey(),entry.getValue());
}
if(ttl>0){
jwtBuilder.setExpiration( new Date(exp));
}
String token = jwtBuilder.compact();
return token;
}
/**
* 解析JWT
* @param token
* @return
*/
public Claims parseJWT(String token){
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token).getBody();
}catch (Exception e){
}
return claims;
}
}
(3) 修改ihrm_common工程的application.yml, 添加配置
jwt:
config:
key: saas-ihrm
ttl: 360000
5.4 登录成功签发token
(1)配置JwtUtil。修改ihrm_system工程的启动类
@Bean
public JwtUtil jwtUtil(){
return new util.JwtUtil();
}
(2)添加登录方法
/**
* 用户登录
* 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);
}
}
(3)测试运行结果
使用postman验证登录返回:
5.5 获取用户信息鉴权
需求:用户登录成功之后,会发送一个新的请求到服务端,获取用户的详细信息。获取用户信息的过程中必须登录
才能,否则不能获取。
前后端约定:前端请求微服务时需要添加头信息Authorization ,内容为Bearer+空格+token
(1)添加响应值对象
//登录成功
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);
}
}
{"success":true,"code":10000,"message":"操作成
功!","data":"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDYyNjYxODkxNjE4Mzc3NzI4Iiwic3ViIjoiemhhb
mdzYW4iLCJpYXQiOjE1NDI0NjgzNzcsImNvbXBhbnlJZCI6IjEiLCJjb21wYW55TmFtZSI6IuS8oOaZuuaSreWu
oiIsImV4cCI6MTU0MjU1NDc3N30.J-8uv8jOp2GMLpBwrUOksnErjA4-DOJ_qvy7tsJbsa8"}
@Getter
@Setter
@NoArgsConstructor
public class ProfileResult {
private String mobile;
private String username;
private String company;
private Map roles;
public ProfileResult(User user) {
this.mobile = user.getMobile();
this.username = user.getUsername();
this.company = user.getCompanyName();
//角色数据
Set<String> menus = new HashSet<>();
Set<String> points = new HashSet<>();
Set<String> apis = new HashSet<>();
Map rolesMap = new HashMap<>();
for (Role role : user.getRoles()) {
for (Permission perm : role.getPermissions()) {
String code = perm.getCode();
if(perm.getType() == 1) {
menus.add(code);
}else if(perm.getType() == 2) {
points.add(code);
}else {
apis.add(code);
}
}
}
rolesMap.put("menus",menus);
rolesMap.put("points",points);
rolesMap.put("apis",points);
this.roles = rolesMap;
}
}
(2)添加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));
}
(3)验证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));
}
https://www.boxuegu.com/promote/detail-1232.html
基于SaaS平台的iHRM实战开发
百度网盘下载链接:
链接: https://pan.baidu.com/s/1repa2B4XYycXNK4pO8QyAw 密码: vl27
如果失效联系v信:cmL46679910