用户授权
需求分析
- 管理员给用户分配权限,权限数据写到数据库中。
- 认证服务在进行用户认证时从数据库读取用户的权限数据(动态数据)
权限数据模型
五张表,标准的权限模型设计。
比较简单易懂。
xc_user
用户表,存储了系统用户信息,用户类型包括:学生、老师、管理员等
xc_role
角色表,存储了系统的角色信息,学生、老师、教学管理员、系统管理员等
xc_user_role
用户角色表,一个用户可拥有多个角色,一个角色可被多个用户所拥有
xc_menu
模块表,记录了菜单及菜单下的权限
xc_permission
角色权限表,一个角色可拥有多个权限,一个权限可被多个角色所拥有
用户中心查询权限列表
Dao
XcMenuMapper
1
2
3
4
5
6
7
8
9
10
11
12package com.xuecheng.ucenter.dao;
import com.xuecheng.framework.domain.ucenter.XcMenu;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
public interface XcMenuMapper {
List<XcMenu> selectPermissionByUserId(String userid);
}XcMenuMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<mapper namespace="com.xuecheng.ucenter.dao.XcMenuMapper">
<select id="selectPermissionByUserId"
resultType=
"com.xuecheng.framework.domain.ucenter.XcMenu" parameterType=
"java.lang.String">
SELECT
id,
CODE,
p_id pId,
menu_name menuName,
url,
is_menu isMenu,
LEVEL,
sort,
STATUS,
icon,
create_time createTime,
update_time updateTime
FROM
xc_menu
WHERE id IN(
SELECT menu_id FROM xc_permission WHERE role_id IN (
SELECT role_id FROM xc_user_role WHERE user_id = #{id}
)
)
</select>
</mapper>
Service
修改UserService#findByUsername
方法,查询用户权限
1 |
|
认证服务设置用户权限
修改认证微服务中授权部分的代码,使用查询到的用户权限进行用户授权。
1 | List<String> stringList = userext.getPermissions() |
异常处理
权限不足为特殊的异常,可以使用统一异常处理器,捕捉异常然后抛出。
修改xc-framework-common
中的ExceptionCatch
中的静态代码块
1 | static { |
新增上AccessDeniedException
的错误码即可。
控制接口权限
控制接口权限需要下列步骤:
- 需要控制接口的微服务,需要先引入
Spring Security
的依赖 - 拷贝
ResourceServerConfig
类 - 拷贝公钥
ResourceServerConfig
需要注解@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
- 在需要控制权限的接口上添加注解
@PreAuthorize("hasAuthority('{permission_str}')")
测试
登录后获取token
,解析token
,查看是否包含permiss
列表
我这里是在JWT
官网解析的,网址:👉https://jwt.io/👈
前端集成认证授权
这里以教学管理中心
模块为例
需求分析
前端集成认证授权功能需要作如下工作:
- 前端页面校验用户的身份,如果用户没有登录则跳转到登录页面
- 前端请求资源服务需要在
http header
中添加jwt令牌
,资源服务根据jwt令牌
完成授权。
哪些功能需要前端请求时携带JWT
?
用户登录成功请求资源服务都需要携带jwt令牌
,因为资源服务已经实现了jwt
认证,如果校验头部没有jwt
则会认为身份不合法。
配置Nginx
教学管理前端访问微服务统一在访问地址前添加/api
前缀并经过网关转发到微服务。
配置teacher.xuecheng.com
的代理。
1 | #前端教学管理 |
前端代码
其他的前端代码基本都是实现了的,只需要在main.js
把注释符去掉就行了。
前端代码就不做多的解析了,有兴趣的可以自己研究。
细粒度授权
感觉叫细粒度授权有点怪怪的,这更像是业务逻辑。
需求分析
- 我的课程查询,细粒度授权过程如下
- 获取当前登录的用户Id
- 得到用户所属教育机构的Id
- 查询该教学机构下的课程信息
- 修改课程管理服务“我的课程”的功能,根据公司Id查询课程
- 修改Dao,支持根据公司Id 查询课程
- 修改Service,将公司Id传入Dao
- 修改Controller,获取当前用户的公司Id,传给Service
获取当前登录用户
JWT令牌解析
因为之前我们已经在登录的时候,将用户的company_id
放在了令牌中,我们现在只需要解析令牌就可以获取到当前登录用户的company_id
工具类代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38package com.xuecheng.framework.utils;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
public class Oauth2Util {
public static Map<String,String> getJwtClaimsFromHeader(HttpServletRequest request) {
if (request == null) {
return null;
}
//取出头信息
String authorization = request.getHeader("Authorization");
if (StringUtils.isEmpty(authorization) || authorization.indexOf("Bearer") < 0) {
return null;
}
//从Bearer 后边开始取出token
String token = authorization.substring(7);
Map<String,String> map = null;
try {
//解析jwt
Jwt decode = JwtHelper.decode(token);
//得到 jwt中的用户信息
String claims = decode.getClaims();
//将jwt转为Map
map = JSON.parseObject(claims, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
将工具解析到的数据封装为想要的数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package com.xuecheng.framework.utils;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
public class XcOauth2Util {
public UserJwt getUserJwtFromHeader(HttpServletRequest request){
Map<String, String> jwtClaims = Oauth2Util.getJwtClaimsFromHeader(request);
if(jwtClaims == null || StringUtils.isEmpty(jwtClaims.get("id"))){
return null;
}
UserJwt userJwt = new UserJwt();
userJwt.setId(jwtClaims.get("id"));
userJwt.setName(jwtClaims.get("name"));
userJwt.setCompanyId(jwtClaims.get("companyId"));
userJwt.setUtype(jwtClaims.get("utype"));
userJwt.setUserpic(jwtClaims.get("userpic"));
return userJwt;
}
public class UserJwt{
private String id;
private String name;
private String userpic;
private String utype;
private String companyId;
}
}
课程管理改造
课程管理新增API
在CourseBaseControllerApi
中新增方法
1 | QueryResponseResult findCourseList(int page, int size, CourseListRequest courseListRequest); |
Dao
修改 CourseMapper.xml
的查询课程列表,添加companyId
条件。
1 | <select id="findCourseListPage" resultType="com.xuecheng.framework.domain.course.ext.CourseInfo" |
CourseBaseService
1 | /** |
需要引入
PageHelper
的依赖
1
2
3
4
5 > <dependency>
> <groupId>com.github.pagehelper</groupId>
> <artifactId>pagehelper-spring-boot-starter</artifactId>
> </dependency>
>
CourseBaseController
实现findCourseList
方法
1 |
|
测试
我从postman
能够成功获取到数据。
用前端去,我发现teacher.xucheng.com
这边的sessionStorage
里面根本没有存令牌不知道为啥,是跨域吗?还是其他问题,搞了一会儿没解决,算了,懒得搞了。
微服务之间认证
微服务之间的调用,也是需要携带jwt令牌
的,但是现在是没有的,所以当我们调用的某些需要调用其他微服务的接口的时候,就会出现401
认证失败的错误。
Feign拦截器
定义Feign拦截器
1 | package com.xuecheng.framework.intercepter; |
使用Feign拦截器
只需要将编写的拦截器注册到需要使用该拦截器的微服务的spring
容器中即可.
不想单拎出来可以直接在启动类中注册.
1 |
|