学习目标:
理解权限管理的需求以及设计思路实现角色分配和权限分配
理解常见的认证机制
能够使用JWT完成微服务Token签发与验证
完成权限(菜单,按钮(权限点),API接口)的基本操作
权限与菜单,菜单与按钮,菜单与API接口都是一对一关系。为了方便操作,在SAAS-HRM系统的表设计中,采用基于共享主键的形式实现一对一关系维护,并且数据库约束,一切的关系维护需要程序员在代码中实现。
在系统微服务中创建权限,菜单,按钮(权限点),API对象的实体类
package com.ihrm.domain.system;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name = "pe_permission")
@Getter
@Setter
@NoArgsConstructor
@DynamicInsert(true)
@DynamicUpdate(true)
public class Permission implements Serializable {
private static final long serialVersionUID = -4990810027542971546L;
/**
* 主键
*/
@Id
private String id;
/**
* 权限名称
*/
private String name;
/**
* 权限类型 1为菜单 2为功能 3为API
*/
private Integer type;
/**
* 权限编码
*/
private String code;
/**
* 权限描述
*/
private String description;
//父权限
private String pid;
//是否查询全部权限 0 :查询全部,1 :只查询企业所属权限
private Integer enVisible;
public Permission(String name, Integer type, String code, String description) {
this.name = name;
this.type = type;
this.code = code;
this.description = description;
}
}
骚戴理解:pid是指的父权限,就是我菜单权限下面可能有一套按钮或者API,那么这些就是子权限,那么它们的父权限就是这个菜单的id。enVisible表示是否查询全部权限(0 :查询全部,1 :只查询企业所属权限)也就是0表示更加高级的权限,连企业都访问不了的,只有超级管理员可以看到的权限
@Entity
@Table(name = "pe_permission_menu")
@Getter
@Setter
public class PermissionMenu implements Serializable {
private static final long serialVersionUID = -1002411490113957485L;
@Id
private String id; //主键
private String menuIcon; //展示图标
private String menuOrder; //排序号
}
@Entity
@Table(name = "pe_permission_point")
@Getter
@Setter
public class PermissionPoint implements Serializable {
private static final long serialVersionUID = -1002411490113957485L;
@Id
private String id;
private String pointClass;
private String pointIcon;
private String pointStatus;
}
@Entity
@Table(name = "pe_permission_api")
@Getter
@Setter
public class PermissionApi implements Serializable {
private static final long serialVersionUID = -1803315043290784820L;
@Id
private String id;
private String apiUrl;
private String apiMethod;
private String apiLevel;//权限等级,1为通用接口权限,2为需校验接口权限
}
/**
* 权限数据访问接口
*/
public interface PermissionDao extends JpaRepository,
JpaSpecificationExecutor {
List findByTypeAndPid(int type,String pid);
}
public interface PermissionMenuDao extends JpaRepository,
JpaSpecificationExecutor {
}
public interface PermissionPointDao extends JpaRepository, JpaSpecificationExecutor {
}
public interface PermissionApiDao extends JpaRepository, JpaSpecificationExecutor {
}
package com.ihrm.system.service;
import com.ihrm.common.entity.ResultCode;
import com.ihrm.common.exception.CommonException;
import com.ihrm.common.utils.BeanMapUtils;
import com.ihrm.common.utils.IdWorker;
import com.ihrm.common.utils.PermissionConstants;
import com.ihrm.domain.system.*;
import com.ihrm.system.dao.*;
import com.ihrm.system.dao.PermissionDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
@Transactional
public class PermissionService {
@Autowired
private PermissionDao permissionDao;
@Autowired
private PermissionMenuDao permissionMenuDao;
@Autowired
private PermissionPointDao permissionPointDao;
@Autowired
private PermissionApiDao permissionApiDao;
@Autowired
private IdWorker idWorker;
/**
* 1.保存权限
*/
public void save(Map map) throws Exception {
//设置主键的值
String id = idWorker.nextId()+"";
//1.通过map构造permission对象
Permission perm = BeanMapUtils.mapToBean(map,Permission.class);
perm.setId(id);
//2.根据类型构造不同的资源对象(菜单,按钮,api)
int type = perm.getType();
switch (type) {
case PermissionConstants.PERMISSION_MENU:
PermissionMenu menu = BeanMapUtils.mapToBean(map,PermissionMenu.class);
menu.setId(id);
permissionMenuDao.save(menu);
break;
case PermissionConstants.PERMISSION_POINT:
PermissionPoint point = BeanMapUtils.mapToBean(map,PermissionPoint.class);
point.setId(id);
permissionPointDao.save(point);
break;
case PermissionConstants.PERMISSION_API:
PermissionApi api = BeanMapUtils.mapToBean(map,PermissionApi.class);
api.setId(id);
permissionApiDao.save(api);
break;
default:
throw new CommonException(ResultCode.FAIL);
}
//3.保存
permissionDao.save(perm);
}
/**
* 2.更新权限
*/
public void update(Map map) throws Exception {
Permission perm = BeanMapUtils.mapToBean(map,Permission.class);
//1.通过传递的权限id查询权限
Permission permission = permissionDao.findById(perm.getId()).get();
permission.setName(perm.getName());
permission.setCode(perm.getCode());
permission.setDescription(perm.getDescription());
permission.setEnVisible(perm.getEnVisible());
//2.根据类型构造不同的资源
int type = perm.getType();
switch (type) {
case PermissionConstants.PERMISSION_MENU:
PermissionMenu menu = BeanMapUtils.mapToBean(map,PermissionMenu.class);
menu.setId(perm.getId());
permissionMenuDao.save(menu);
break;
case PermissionConstants.PERMISSION_POINT:
PermissionPoint point = BeanMapUtils.mapToBean(map,PermissionPoint.class);
point.setId(perm.getId());
permissionPointDao.save(point);
break;
case PermissionConstants.PERMISSION_API:
PermissionApi api = BeanMapUtils.mapToBean(map,PermissionApi.class);
api.setId(perm.getId());
permissionApiDao.save(api);
break;
default:
throw new CommonException(ResultCode.FAIL);
}
//3.保存
permissionDao.save(permission);
}
/**
* 3.根据id查询
* //1.查询权限
* //2.根据权限的类型查询资源
* //3.构造map集合
*/
public Map findById(String id) throws Exception {
Permission perm = permissionDao.findById(id).get();
int type = perm.getType();
Object object = null;
if(type == PermissionConstants.PERMISSION_MENU) {
object = permissionMenuDao.findById(id).get();
}else if (type == PermissionConstants.PERMISSION_POINT) {
object = permissionPointDao.findById(id).get();
}else if (type == PermissionConstants.PERMISSION_API) {
object = permissionApiDao.findById(id).get();
}else {
throw new CommonException(ResultCode.FAIL);
}
Map map = BeanMapUtils.beanToMap(object);
map.put("name",perm.getName());
map.put("type",perm.getType());
map.put("code",perm.getCode());
map.put("description",perm.getDescription());
map.put("pid",perm.getPid());
map.put("enVisible",perm.getEnVisible());
return map;
}
/**
* 4.查询全部
* type : 查询全部权限列表type:0:菜单 + 按钮(权限点) 1:菜单2:按钮(权限点)3:API接口
* enVisible : 0:查询所有saas平台的最高权限,1:查询企业的权限
* pid :父id
*/
public List findAll(Map map) {
//1.需要查询条件
Specification spec = new Specification() {
/**
* 动态拼接查询条件
* @return
*/
public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List list = new ArrayList<>();
//根据父id查询
if(!StringUtils.isEmpty(map.get("pid"))) {
list.add(criteriaBuilder.equal(root.get("pid").as(String.class),(String)map.get("pid")));
}
//根据enVisible查询
if(!StringUtils.isEmpty(map.get("enVisible"))) {
list.add(criteriaBuilder.equal(root.get("enVisible").as(String.class),(String)map.get("enVisible")));
}
//根据类型 type
if(!StringUtils.isEmpty(map.get("type"))) {
String ty = (String) map.get("type");
CriteriaBuilder.In
骚戴理解:需要注意的是菜单、按钮、api这三个子权限的id要和父权限id保持一致,也就是PermissionApi、PermissionMenu、PermissionPoint这三个类的id和Permission类保持一致!
把下面这段Java代码翻译成SQL更好理解
if(!StringUtils.isEmpty(map.get("type"))) {
String ty = (String) map.get("type");
CriteriaBuilder.In
这段Java代码是用于构建JPA动态查询的条件语句。它的作用是根据map中的"type"键值来判断是否需要加入一个子条件。
代码逻辑如下:
以下是该Java代码对应的SQL语句:
SELECT*FROM 表名 WHERE type IN (1, 2) -- 当map.get("type")中的值为"0"时
SELECT*FROM 表名 WHERE type = ? -- 当map.get("type")中的值为除"0"以外的值时
这段Java代码会在生成的SQL语句中自动完成参数的绑定,具体SQL语句中的占位符?由框架来替换成具体的值。
package com.ihrm.system.controller;
import com.ihrm.common.entity.PageResult;
import com.ihrm.common.entity.Result;
import com.ihrm.common.entity.ResultCode;
import com.ihrm.domain.system.Permission;
import com.ihrm.domain.system.User;
import com.ihrm.system.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
//1.解决跨域
@CrossOrigin
//2.声明restContoller
@RestController
//3.设置父路径
@RequestMapping(value="/sys")
public class PermissionController {
@Autowired
private PermissionService permissionService;
/**
* 保存
*/
@RequestMapping(value = "/permission", method = RequestMethod.POST)
public Result save(@RequestBody Map map) throws Exception {
permissionService.save(map);
return new Result(ResultCode.SUCCESS);
}
/**
* 修改
*/
@RequestMapping(value = "/permission/{id}", method = RequestMethod.PUT)
public Result update(@PathVariable(value = "id") String id, @RequestBody Map map) throws Exception {
//构造id
map.put("id",id);
permissionService.update(map);
return new Result(ResultCode.SUCCESS);
}
/**
* 查询列表
*/
@RequestMapping(value = "/permission", method = RequestMethod.GET)
public Result findAll(@RequestParam Map map) {
List list = permissionService.findAll(map);
return new Result(ResultCode.SUCCESS,list);
}
/**
* 根据ID查询
*/
@RequestMapping(value = "/permission/{id}", method = RequestMethod.GET)
public Result findById(@PathVariable(value = "id") String id) throws Exception {
Map map = permissionService.findById(id);
return new Result(ResultCode.SUCCESS,map);
}
/**
* 根据id删除
*/
@RequestMapping(value = "/permission/{id}", method = RequestMethod.DELETE)
public Result delete(@PathVariable(value = "id") String id) throws Exception {
permissionService.deleteById(id);
return new Result(ResultCode.SUCCESS);
}
}
骚戴理解:这里我一开始搞错了,我以为下面的这个前端的Permission实体就是对应这后端的Permission对象,结果不是!导致我一直不能理解为什么后端要封装成一个map来接受参数而不是直接用Permission对象接收,这个前端的Permission实体其实是后端的Permission、PermissionApi、PermissionMenu、PermissionPoint的合体集合
所以这个前端的Permission实体可以用map来接收,也就是通过这么一个前端的Permission实体实现菜单、组件、API的管理,根据前端传过来的type来判断到底要操作什么,这也就是为什么就只有PermissionController这一个控制器却可以实现菜单、组件、API的增删改查操作
将资料module-permissions引入到工程的/src文件夹下,在/src/main.js完成模块注册
import permissions from '@/module-permissions/' // 权限管理
Vue.use(permissions, store)
在/src/api/base/目录下创建permissions.js
import {createAPI} from '@/utils/request'
const api = "/sys/permission"
export const list = data => createAPI(`${api}`, 'get', data)
export const add = data => createAPI(`${api}`, 'post', data)
export const update = data => createAPI(`${api}/${data.id}`, 'put', data)
export const remove = data => createAPI(`${api}/${data.id}`, 'delete', data)
export const detail = data => createAPI(`${api}/${data.id}`, 'get', data)
export const saveOrUpdate = data => {return data.id?update(data):add(data)}
添加菜单
{{scope.row.name}}
添加权限点
查看api权限
查看
删除
添加api权限
查看
删除
由于使用了RBAC模型对权限进行统一管理,所以每个SAAS-HRM平台的用户都应该具有角色的信息。进而通过角 色完成对权限的识别。众所周知,一个用户可以具有很多的角色,一个角色可以被分配给不同的用户。所以用户和角色之间是多对多关系。
在com.ihrm.system.domain.User用户实体类中添加与角色的多对多关系并进行JPA的配置
@ManyToMany
@JsonIgnore
@JoinTable(name="pe_user_role",joinColumns=
{@JoinColumn(name="user_id",referencedColumnName="id")},
inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")}
)
private Set roles = new HashSet();//用户与角色 多对多
在com.ihrm.system.domain.Role角色实体类中配置角色与用户的多对多关系并进行JPA配置
@JsonIgnore
@ManyToMany(mappedBy="roles")
private Set users = new HashSet(0);//角色与用户 多对多
骚戴理解:通过在实体类里这样配置就可以实现多个表关联自动修改,也就是你只需要修改实体类他就会自动去维护修改数据库中的关联表和中间表
/**
* 分配角色
*/
@RequestMapping(value = "/user/assignRoles", method = RequestMethod.PUT)
public Result assignRoles(@RequestBody Map map) {
//1.获取被分配的用户id
String userId = (String) map.get("id");
//2.获取到角色的id列表
List roleIds = (List) map.get("roleIds");
//3.调用service完成角色分配
userService.assignRoles(userId,roleIds);
return new Result(ResultCode.SUCCESS);
}
/**
* 分配角色
*/
public void assignRoles(String userId,List roleIds) {
//1.根据id查询用户
User user = userDao.findById(userId).get();
//2.设置用户的角色集合
Set roles = new HashSet<>();
for (String roleId : roleIds) {
Role role = roleDao.findById(roleId).get();
roles.add(role);
}
//设置用户和角色集合的关系
user.setRoles(roles);
//3.更新用户
userDao.save(user);
}
{{item.name}}
完成对角色权限的分配。
@JsonIgnore //忽略json转化
@ManyToMany
@JoinTable(name="pe_role_permission",
joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")},
inverseJoinColumns=
{@JoinColumn(name="permission_id",referencedColumnName="id")})
private Set permissions = new HashSet(0);//角色与模块 多对多
/**
* 分配权限
*/
@RequestMapping(value = "/role/assignPrem", method = RequestMethod.PUT)
public Result assignPrem(@RequestBody Map map) {
//1.获取被分配的角色的id
String roleId = (String) map.get("id");
//2.获取到权限的id列表
List permIds = (List) map.get("permIds");
//3.调用service完成权限分配
roleService.assignPerms(roleId,permIds);
return new Result(ResultCode.SUCCESS);
}
/**
* 分配权限
*/
public void assignPerms(String roleId,List permIds) {
//1.获取分配的角色对象
Role role = roleDao.findById(roleId).get();
//2.构造角色的权限集合
Set perms = new HashSet<>();
for (String permId : permIds) {
Permission permission = permissionDao.findById(permId).get();
//需要根据父id和类型查询API权限列表
List apiList =
permissionDao.findByTypeAndPid(PermissionConstants.PERMISSION_API, permission.getId());
perms.addAll(apiList);//自定赋予API权限
perms.add(permission);//当前菜单或按钮的权限
}
System.out.println(perms.size());
//3.设置角色和权限的关系
role.setPermissions(perms);
//4.更新角色
roleDao.save(role);
}
骚戴理解:上面的分配权限的逻辑和分配角色的逻辑基本一样的,区别就在于下面这两行代码,如果这个权限是按钮,那么这个按钮一定是绑定了一个api权限的,所以如果前端赋按钮权限,那么api权限也要一起赋予,这里一开始我觉得按钮权限和api权限是一对一的关系,所以不能理解为什么用List来接收,后面发现有的时候一个按钮会发多个请求,例如“分配角色”按钮就会发两个请求
List apiList = permissionDao.findByTypeAndPid(PermissionConstants.PERMISSION_API, permission.getId());
perms.addAll(apiList);//自定赋予API权限
package com.ihrm.system.dao;
import com.ihrm.domain.system.Permission;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
* 权限
*/
public interface PermissionDao extends JpaRepository, JpaSpecificationExecutor {
List findByTypeAndPid(int type,String pid);
}
骚戴理解:这里直接在PermissionDao里面写了findByTypeAndPid方法,但是没有写具体的sql,我当时就很纳闷他是怎么实现的,这其实就是根据这个方法的命名来实现的,findByTypeAndPid他会根据这个方法名解析出Type和Pid字段,然后用and拼接成sql语句,实现的效果就是按这两个值去查询
分配权限
修改
删除
export const assignPrem = data => createAPI(`/sys/role/assignPrem`, 'put', data)
HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie 对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏 览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效
OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资 源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。 OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频 编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样, OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证 权限管理的企业应用。
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
Token Auth的优点
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。在Java世界中通过JJWT实现JWT创建和验证。
io.jsonwebtoken
jjwt
0.6.0
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,在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
我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims
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() );
}
}
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()) );
}
}
在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 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 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;
}
}
jwt:
config:
key: saas-ihrm
ttl: 360000
@Bean
public JwtUtil jwtUtil(){
return new util.JwtUtil();
}
/**
* 用户登录
* 1.通过service根据mobile查询用户
* 2.比较password
* 3.生成jwt信息
*
*/
@RequestMapping(value="/login",method = RequestMethod.POST)
public Result login(@RequestBody Map 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 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
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 roles = new HashMap<>();
public ProfileResult(User user) {
this.mobile = user.getMobile();
this.username = user.getUsername();
this.company = user.getCompanyName();
Set roles = user.getRoles();
Set menus = new HashSet<>();
Set points = new HashSet<>();
Set apis = new HashSet<>();
for (Role role : roles) {
Set 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);
}
}
/**
* 获取个人信息
*/
@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));
}
思路:从请求中获取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));
}