Loading

Day18_用户授权

1 用户授权业务流程

用户授权的业务流程如下:

业务流程说明如下:

1、用户认证通过,认证服务向浏览器cookie写入token( 身份令牌)
2、前端携带token请求用户中心服务获取jwt令牌
前端获取到jwt令牌解析,并存储在sessionStorage
3、前端携带cookie中的身份令牌及jwt令牌访问资源服务
前端请求资源服务需要携带两个token,一个是cookie中的身份令牌,一个是http header中的jwt。
前端请求资源服务前在http header上添加jwt请求资源。
4、网关校验token的合法性
用户请求必须携带身份令牌和jwt令牌。
网关校验redis中user_token的有效期,已过期则要求用户重新登录。
5、资源服务校验jwt的合法性并进行授权
资源服务校验jwt令牌,完成授权,拥有权限的方法正常执行,没有权限的方法将拒绝访问。

2 方法授权

2.1 需求分析

方法授权要完成的是资源服务根据jwt令牌完成对方法的授权,具体流程如下:

1、生成Jwt令牌时在令牌中写入用户所拥有的权限

我们给每个权限起个名字,例如某个用户拥有如下权限:

course_find_list:课程查询

course_pic_list:课程图片查询

2、在资源服务方法上添加注解PreAuthorize,并指定此方法所需要的权限

例如下边是课程管理接口方法的授权配置,它就表示要执行这个方法需要拥有course_find_list权限。

@PreAuthorize("hasAuthority('course_find_list')") @Override public QueryResult<CourseInfo> findCourseList(@PathVariable("page") int page,@PathVariable("size") int size, CourseListRequest courseListRequest)

3、当请求有权限的方法时正常访问

4、当请求没有权限的方法时则拒绝访问

2.2 jwt令牌包含权限

修改认证服务的UserDetailServiceImpl类,下边的代码中 permissionList列表中存放了用户的权限,并且将权限标识按照中间使用逗号分隔的语法组成一个字符串,最终提供给Spring security。

......
      List<XcMenu> permissions = userext.getPermissions();
        List<String> user_permission = new ArrayList<>();
        permissions.forEach(item-> user_permission.add(item.getCode()));
        user_permission.add("course_get_baseinfo");
        user_permission.add("course_find_pic");
        String user_permission_string  = StringUtils.join(user_permission.toArray(), ",");
        UserJwt userDetails = new UserJwt(username,
                password,
                AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));
        userDetails.setId(userext.getId());
        userDetails.setUtype(userext.getUtype());//用户类型
......

重启认证服务工程,使用postman完成登录,从redis中找到jwt令牌。

使用jwt的测试程序查看此令牌的内容。

可以看到authorities属性中为用户的权限。

2.3 方法授权实现

2.3.1 资源服务添加授权控制

1、要想在资源服务使用方法授权,首先在资源服务配置授权控制

1)添加spring-cloud-starter-oauth2依赖。

2)拷贝授权配置类ResourceServerConfig。

3)拷贝公钥。

2.3.2 方法上添加注解

通常情况下,程序员编写在资源服务的的controller方法时会使用注解指定此方法的权限标识。

1、查询课程列表方法

指定查询课程列表方法需要拥有course_find_list权限。

    @PreAuthorize("hasAuthority('course_find_list')")
    @Override
    @GetMapping("/category/list")
    public CategoryNode findList() {
        return categoryService.findList();
    }

2、查看课程基本信息方法

指定查询课程基本信息方法需要拥有course_get_baseinfo权限。

    @PreAuthorize("hasAuthority('course_get_baseinfo')")
    @Override
    @GetMapping("/coursebase/get/{courseId}")
    public CourseBase getCourseBaseById(@PathVariable("courseId") String courseId) throws RuntimeException {
        return courseService.getCoursebaseById(courseId);
    }

3、在资源服务(这里是课程管理)的ResourceServerConfig类上添加注解,激活方法上添加授权注解

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解

2.4 方法授权测试

重启课程管理服务,测试上边两个方法。

使用postman测试,测试前执行登录,并且将jwt令牌添加到header。

1)Get 请求 http://www.xuecheng.com/api/course/coursebase/get/4028e581617f945f01617f9dabc40000

  1. Get请求 http://www.xuecheng.com/api/course/coursebase/list/1/2

由于用户没有查询课程列表方法的权限,所以无法正常访问,其它方法可以正常访问。

控制台报错:

org.springframework.security.access.AccessDeniedException: 不允许访问

说明:如果方法上没有添加授权注解spring security将不进行授权控制,只要jwt令牌合法则可以正常访问。

3)异常处理

上边当没有权限访问时资源服务应该返回下边的错误代码:

UNAUTHORISE(false,10002,"权限不足,无权操作!")

进入资源服务(这里是课程管理),添加异常类AccessDeniedException.class与错误代码 10002 的 对应关系。

package com.xuecheng.manage_course.exception;


import com.xuecheng.framework.exception.ExceptionCatch;
import com.xuecheng.framework.model.response.CommonCode;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;

/**课程管理自定义的异常类,其中定义异常类型所对应的错误代码
 * @author Administrator
 * @version 1.0
 **/
@ControllerAdvice//控制器增强
public class CustomExceptionCatch  extends ExceptionCatch {
    static {
        builder.put(AccessDeniedException.class, CommonCode.UNAUTHORISE);
    }
}

再次测试,结果如下:

2.5 小结

方法授权步骤:

1、ResourceServerConfig类上添加注解,如下:

//激活方法上的PreAuthorize注解 
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

2、在方法添加授权注解

@PreAuthorize("hasAuthority('???')")

3、如果方法上不添加授权注解表示此方法不需要权限即可访问

3 动态查询用户权限

3.1 需求分析

截至目前在测试授权时使用的权限数据是静态数据,正常情况的流程是:

1、管理员给用户分配权限,权限数据写到数据库中
2、认证服务在进行用户认证时从数据库读取用户的权限数据(动态数据)

本节实现动态权限数据。

3.2 权限数据模型

3.2.1 数据模型结构

打开xc_user数据库,找到下边的表:

xc_user:用户表,存储了系统用户信息,用户类型包括:学生、老师、管理员等
xc_role:角色表,存储了系统的角色信息,学生、老师、教学管理员、系统管理员等
xc_user_role:用户角色表,一个用户可拥有多个角色,一个角色可被多个用户所拥有
xc_menu:模块表,记录了菜单及菜单下的权限
xc_permission:角色权限表,一个角色可拥有多个权限,一个权限可被多个角色所拥有

3.2.2 数据模型的使用

本项目教学阶段不再实现权限定义及用户权限分配的功能,但是基于权限数据模型(5张数据表)及现有数据,要求学生在数据库中操作完成给用户分配权限、查询用户权限等需求。

1、查询用户所拥有的权限

步骤:

确定用户的id
查询用户所拥有的角色
查询用户所拥有的权限

例子:

SELECT * 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 = '49' ) )

2、给用户分配权限

1)向已拥有角色分配权限步骤:

确定用户的id
确定权限的id
确定用户的角色
向角色权限表添加记录

2)添加角色给用户分配权限步骤:

确定用户的id
确定权限的id
添加角色
向角色权限表添加记录
向用户角色关系表添加记录

3.3 用户中心查询用户权限

3.3.1 需求分析

认证服务请求用户中心查询用户信息,用户需要将用户基本信息和用户权限一同返回给认证服务。

本小节实现用户查询查询用户权限,并将用户权限信息添加到的用户信息中返回给认证服务。

以上需求需要修改如下接口:

package com.xuecheng.api.ucenter;

import com.xuecheng.framework.domain.ucenter.ext.XcUserExt;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

/**
 * Created by Administrator.
 */
@Api(value = "用户中心",description = "用户中心管理")
public interface UcenterControllerApi {
    @ApiOperation("根据用户账号查询用户信息")
    public XcUserExt getUserext(String username);
}

3.3.2 DAO

在用户中心服务中编写dao,实现根据用户id查询权限。

1、定义XcMenuMapper.java

在com.xuecheng.ucenter.dao包下定义:

package com.xuecheng.ucenter.dao;

import com.xuecheng.framework.domain.ucenter.XcMenu;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * Created by Administrator.
 */
@Mapper
public interface XcMenuMapper {
    //根据用户id查询用户的权限
    public List<XcMenu> selectPermissionByUserId(String userid);
}

2、XcMenuMapper.xml

在com.xuecheng.ucenter.dao下定义XcMenuMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xuecheng.ucenter.dao.XcMenuMapper" >


    <select id="selectPermissionByUserId" parameterType="java.lang.String"
            resultType="com.xuecheng.framework.domain.ucenter.XcMenu">

    SELECT
        id,
        CODE,
        p_id pId,
        menu_name menuName,
        url,
        is_menu isMenu,
        LEVEL,
        sort,
        STATUS,
        icon,
        create_time createTime,
        update_time updateTiem
    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>

其它Dao采用spring data 编写如下:

这个开发没用到那么多DAO

3.3.3 Service

修改UserService的getUserExt方法,查询用户权限。

		//根据账号查询用户信息
    public XcUserExt getUserExt(String username){
        //根据账号查询xcUser信息
        XcUser xcUser = this.findXcUserByUsername(username);
        if(xcUser == null){
            return null;
        }
        //用户id
        String userId = xcUser.getId();
        //查询用户所有权限
        List<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(userId);

        //根据用户id查询用户所属公司id
        XcCompanyUser xcCompanyUser = xcCompanyUserRepository.findByUserId(userId);
        //取到用户的公司id
        String companyId = null;
        if(xcCompanyUser!=null){
            companyId = xcCompanyUser.getCompanyId();
        }
        XcUserExt xcUserExt = new XcUserExt();
        BeanUtils.copyProperties(xcUser,xcUserExt);
        xcUserExt.setCompanyId(companyId);
        //设置权限
        xcUserExt.setPermissions(xcMenus);
        return xcUserExt;
    }

3.4 认证服务查询用户权限

修改认证服务的UserDetailServiceImpl,查询用户的权限,并拼接权限串,将原来硬编码权限代码删除,代码如下:

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //取出身份,如果身份为空说明没有认证
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证client_id和client_secret
        if (authentication == null) {
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
            if (clientDetails != null) {
                //密码
                String clientSecret = clientDetails.getClientSecret();
                return new User(username, clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList(""));
            }
        }
        if (StringUtils.isEmpty(username)) {
            return null;
        }
        //远程调用用户中心根据账号查询用户信息
        XcUserExt userext = userClient.getUserext(username);
        if (userext == null) {
            //返回空给spring security表示用户不存在
            return null;
        }
//        XcUserExt userext = new XcUserExt();
//        userext.setUsername("itcast");
//        userext.setPassword(new BCryptPasswordEncoder().encode("123"));
        //userext.setPermissions(new ArrayList<XcMenu>());//权限暂时用静态的

        //取出正确密码(hash值)
        String password = userext.getPassword();
        //这里暂时使用静态密码
//       String password ="123";
        //用户权限,这里暂时使用静态数据,最终会从数据库读取
        //从数据库获取权限
        List<XcMenu> permissions = userext.getPermissions();
        if (permissions == null) {
            permissions = new ArrayList<>();
        }
        List<String> user_permission = new ArrayList<>();
        permissions.forEach(item -> user_permission.add(item.getCode()));
        //使用静态的权限表示用户所拥有的权限
//        user_permission.add("course_get_baseinfo");//查询课程信息
//        user_permission.add("course_pic_list");//图片查询
        String user_permission_string = StringUtils.join(user_permission.toArray(), ",");
        UserJwt userDetails = new UserJwt(username,
                password,
                AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));
        userDetails.setId(userext.getId());
        userDetails.setUtype(userext.getUtype());//用户类型
        userDetails.setCompanyId(userext.getCompanyId());//所属企业
        userDetails.setName(userext.getName());//用户名称
        userDetails.setUserpic(userext.getUserpic());//用户头像
       /* UserDetails userDetails = new org.springframework.security.core.userdetails.User(username,
                password,
                AuthorityUtils.commaSeparatedStringToAuthorityList(""));*/
//                AuthorityUtils.createAuthorityList("course_get_baseinfo","course_get_list"));
        return userDetails;
    }

3.5 测试

1、执行登录,在redis中查看jwt令牌,使用jwt测试程序解析jwt令牌中是否包括用户的权限

2、使用新的jwt令牌测试方法授权

3、给用户分配新权限,重新生成jwt令牌,测试授权

4 前端集成认证授权

4.1 需求分析

截至目前认证授权服务端的功能已基本完成,本章实现前端集成认证授权功能。

前端集成认证授权功能需要作如下工作:

1、前端页面校验用户的身份,如果用户没有登录则跳转到登录页面
2、前端请求资源服务需要在http header中添加jwt令牌,资源服务根据jwt令牌完成授权

哪些功能需要前端请求时携带JWT?

用户登录成功请求资源服务都需要携带jwt令牌,因为资源服务已经实现了jwt认证,如果校验头部没有jwt则会认为身份不合法。

4.2 教学管理中心

本节实现教学管理中心实现身份校验,其它前端参考教学管理中心实现。

4.2.1 配置虚拟主机

教学管理前端访问微服务统一在访问地址前添加/api前缀并经过网关转发到微服务。

配置teacher.xuecheng.com虚拟主机。

#前端教学管理 
    upstream teacher_server_pool{
      server 127.0.0.1:12000 weight=10;
    }

    #文件服务 
    upstream filesystem_server_pool{
        server 127.0.0.1:22100 weight=10;
    }

    #媒资服务 
    upstream media_server_pool{
        server 127.0.0.1:31400 weight=10;
    }
    #学成网教学管理中心
    server {
    listen 80; 
    server_name teacher.xuecheng.com; 
    #个人中心 
    location / {
     proxy_pass http://teacher_server_pool; 
    } 
    location /api {
      proxy_pass http://api_server_pool; 
    }

    location /filesystem {
     proxy_pass http://filesystem_server_pool; 
    }
    #媒资管理
    location ^~ /api/media/ {
        proxy_pass http://media_server_pool/media/; 
    }
     #认证 
    location ^~ /openapi/auth/ {
        proxy_pass http://auth_server_pool/auth/; 
    }
}

还要配置端口映射。127.0.0.1 teach.xuecheng.com

4.2.2 身份校验

教学管理中心是单页面应用,我们在路由变化时校验用户的身份,校验失败将跳转到登录页面。

校验方法如下:

1、如果成功从sessionStorage和cookie获取当前用户则继续访问
2、如果sessionStorage中无当前用户,cookie中有当前用户则请求服务端获取jwt,如果成功则继续访问
3、以上两种情况都不满足则跳转到登录页面

修改前端xc-ui-pc-teach。

在main.js中添加路由监控代码,如下:

router.beforeEach((to, from, next) => {
  if(openAuthenticate){

    // console.log(to)
    // console.log(from)
    //***********身份校验***************
    let activeUser
    let uid
    try{
      activeUser = utilApi.getActiveUser()
      uid = utilApi.getCookie("uid")
    }catch(e){
      //alert(e)
    }
    if(activeUser && uid && uid == activeUser.uid) {
      next();
    }else if(to.path =='/login' || to.path =='/logout'){
      next();
    }else if(uid){

      //请求获取jwt
      systemApi.getjwt().then((res)=>{
        if(res.success){
          let jwt = res.jwt;
          let activeUser = utilApi.getUserInfoFromJwt(jwt)
          if(activeUser){
            utilApi.setUserSession("activeUser",JSON.stringify(activeUser))
          }
          next();
        }else{
          //跳转到统一登陆
          window.location = "http://ucenter.xuecheng.com/#/login?returnUrl="+ Base64.encode(window.location)
          next();
        }
      })
    }else{
      //跳转到统一登陆
      window.location = "http://ucenter.xuecheng.com/#/login?returnUrl="+ Base64.encode(window.location)
      next();
    }
  }else{
    next();
  }

});

将/config/sysConfig.js中的openAuthenticate值改为true。

2、在base/api/system.js中添加getjwt方法

/*获取jwt令牌*/
export const getjwt= () => {
  return http.requestQuickGet('/openapi/auth/userjwt')
}

3、在utils.js中添加如下方法

getActiveUser: function(){
    let uid = this.getCookie("uid")
    if(uid){
      let activeUserStr = this.getUserSession("activeUser");
      return JSON.parse(activeUserStr);
    }else{
      this.delUserSession("activeUser")
    }
  },
//获取jwt令牌
  getJwt : function(){
    let activeUser = this.getActiveUser()
    if(activeUser){
      return activeUser.jwt
    }
  },
  //解析jwt令牌,获取用户信息
  getUserInfoFromJwt : function (jwt) {
    if(!jwt){
      return ;
    }
    var jwtDecodeVal = jwtDecode(jwt);
    if (!jwtDecodeVal) {
      return ;
    }
    let activeUser={}
    //console.log(jwtDecodeVal)
    activeUser.utype = jwtDecodeVal.utype || '';
    activeUser.username = jwtDecodeVal.name || '';
    activeUser.userpic = jwtDecodeVal.userpic || '';
    activeUser.userid = jwtDecodeVal.userid || '';
    activeUser.authorities = jwtDecodeVal.authorities || '';
    activeUser.uid = jwtDecodeVal.jti || '';
    activeUser.jwt = jwt;
    return activeUser;
  },

4、测试

1)启动学习中心前端、教学管理前端、认证服务、用户中心服务、网关、Eureka

a、进入首页

b、点击“教学提供方”,此时由于没有登录自动跳转到登录页面

c、输入账号和密码登录

登录成功,跳转到教学管理页面

4.2.3 携带JWT授权

1、前端携带JWT请求

根据需求,在使用axios进行http请求前向header中加入jwt令牌

在main.js中添加

// 添加请求拦截器,实现http请求添加Authorization头信息
axios.interceptors.request.use(function (config) {
  // 在发送请求向header添加jwt
  let jwt = utilApi.getJwt()
  if(jwt){
    config.headers['Authorization'] = 'Bearer '+jwt
  }
  return config;
}, function (error) {
  return Promise.reject(error);
});

2、测试http请求是否携带jwt

进入教学管理中心,点击我的课程,观察request header中是否有Authorization信息

3、测试授权效果

当访问一个没有权限的方法时是否报错?

测试方法:

在课程计划查询方法上添加授权注解,表示当前用户需要拥有course_teachplan_list权限方可正常访问。

		@PreAuthorize("hasAuthority('course_teachplan_list')")
    @Override
    @GetMapping("/teachplan/list/{courseId}")
    public TeachplanNode findTeachplanList(@PathVariable("courseId") String courseId) {
        return courseService.findTeachplanList(courseId);

    }

进入我的课程,点击课程计划,观察响应结果为10002错误。

4、提示权限不足

当权限不足首页要给出提示,实现思路是使用axios的拦截,在执行后校验响应结果,如果是10002代码的错误则 提示用户“权限不足”,如果是10001代码则强制登录。

在main.js中添加

// 响应拦截
axios.interceptors.response.use(data => {
  console.log("data=")
  console.log(data)
  if(data && data.data){
    if(data.data.code && data.data.code =='10001'){
      //需要登录
      // router.push({
      //   path: '/login',
      //   query: {returnUrl: Base64.encode(window.location)}
      // })
      window.location = "http://ucenter.xuecheng.com/#/login?returnUrl="+ Base64.encode(window.location)
    }else if(data.data.code && data.data.code =='10002'){
      Message.error('您没有此操作的权限,请与客服联系!');
    }else if(data.data.code && data.data.code =='10003'){
      Message.error('认证被拒绝,请重新登录重试!');
    }
  }
  return data
})

测试:

执行一个没有权限的操作,提示如下:

3 细粒度授权

3.1 需求分析

什么是细粒度授权?

细粒度授权也叫数据范围授权,即不同的用户所拥有的操作权限相同,但是能够操作的数据范围是不一样的。一个例子:用户A和用户B都是教学机构,他们都拥有“我的课程”权限,但是两个用户所查询到的数据是不一样的。

本项目有哪些细粒度授权?

比如:

我的课程,教学机构只允许查询本教学机构下的课程信息。

我的选课,学生只允许查询自己所选课。

如何实现细粒度授权?

细粒度授权涉及到不同的业务逻辑,通常在service层实现,根据不同的用户进行校验,根据不同的参数查询不同的数据或操作不同的数据。

3.2 我的课程细粒度授权

3.2.1 需求分析

1、我的课程查询,细粒度授权过程如下:

1)获取当前登录的用户Id
2)得到用户所属教育机构的Id
3)查询该教学机构下的课程信息

最终实现了用户只允许查询自己机构的课程信息。

2、修改课程管理服务“我的课程”的功能,根据公司Id查询课程,思路如下:

1)修改Dao,支持根据公司Id 查询课程。
2)修改Service,将公司Id传入Dao。
3)修改Controller,获取当前用户的公司Id,传给Service。

3、数据模型分析如下:

1)课程表

在xc_course数据库的course_base 表中添加company_id字段,来表示此课程的归属

2)用户企业表

在xc_user数据库的xc_company_user表中记录了用户的归属公司信息

通过xc_company_user表可得到用户的所属公司Id。

如何查询某个用户的课程?

1、确定用户的Id
2、根据用户的Id查询用户归属的公司
3、根据公司Id查询该公司下的课程信息

一个例子:

/*确定用户的id:49*/
/*根据用户Id查找所属公司*/
SELECT company_id FROM xc_user.xc_company_user WHERE user_id = '49'
/*根据公司查询所拥有的课程*/
SELECT * FROM xc_course.course_base WHERE company_id = '1'

3.2.2 Api

定义我的课程查询接口如下:

@ApiOperation("查询我的课程列表")
    public QueryResponseResult findCourseList(
            int page,
            int size,
            CourseListRequest courseListRequest);

代码中已经定义了

3.2.3 Dao

修改 CourseMapper.xml的查询课程列表,添加companyId条件。

<select id="findCourseListPage" resultType="com.xuecheng.framework.domain.course.ext.CourseInfo"
            parameterType="com.xuecheng.framework.domain.course.request.CourseListRequest">
        SELECT course_base.*, (SELECT pic FROM course_pic WHERE courseid = course_base.id) pic FROM course_base where
        1=1
        <if test="companyId!=null and companyId!=''">
            and course_base.company_id = #{companyId}
        </if>
    </select>

3.2.4 Service

修改CourseService的findCourseList方法,添加companyId参数,并且传给dao。

public QueryResponseResult findCourseList(String companyId, int page, int size, CourseListRequest courseListRequest) {
        if (courseListRequest == null) {
            courseListRequest = new CourseListRequest();
        }

        //企业id
        courseListRequest.setCompanyId(companyId);
        //将companyId传给dao
        courseListRequest.setCompanyId(companyId);

        if (page <= 0) {
            page = 0;
        }
        if (size <= 0) {
            size = 20;
        }
        //设置分页参数
        PageHelper.startPage(page, size);
        //分页查询
        Page<CourseInfo> courseListPage = courseMapper.findCourseListPage(courseListRequest);
        //查询列表
        List<CourseInfo> list = courseListPage.getResult();
        //总记录数
        long total = courseListPage.getTotal();
        //查询结果集
        QueryResult<CourseInfo> courseIncfoQueryResult = new QueryResult<CourseInfo>();
        courseIncfoQueryResult.setList(list);
        courseIncfoQueryResult.setTotal(total);
        return new QueryResponseResult(CommonCode.SUCCESS, courseIncfoQueryResult);
    }

3.2.5 Controller

修改CourseController的findCourseList,向service传入companyId。

这里先使用静态数据测试使用。

		@PreAuthorize("hasAuthority('course_find_list')")
    @Override
    @GetMapping("/coursebase/list/{page}/{size}")
    public QueryResponseResult findCourseList(@PathVariable("page") int page, 		@PathVariable("size") int size, CourseListRequest courseListRequest) {

        //先使用静态数据测试
        String companyId = "1";

        return courseService.findCourseList(companyId, page, size, courseListRequest);
    }

3.2.6 测试

1、用户登录

由于使用了静态数据companyId为1,所以要使用企业编号为1的下边的用户去登录。

2、进入我的课程,查看数据是否正确

观察所查询到的课程是该企业下的课程。

4 微服务之间认证

4.1 需求分析

前边章节已经实现了用户携带身份令牌和JWT令牌访问微服务,微服务获取jwt并完成授权。

当微服务访问微服务,此时如果没有携带JWT则微服务会在授权时报错。

测试课程预览:

1、将课程管理服务和CMS全部添加授权配置
2、用户登录教学管理前端,进入课程发布界面,点击课程发布,观察课程管理服务端报错如下:
feign.FeignException: status 401 reading CmsPageClient#save(CmsPage); content:
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

分析原因:

由于课程管理访问CMS时没有携带JWT令牌导致。

解决方案:

微服务之间进行调用时需携带JWT。

4.2 Feign 拦截器

4.2.1 定义Feign拦截器

微服务之间使用feign进行远程调用,采用feign拦截器实现远程调用携带JWT。

在common工程添加依赖:

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在Common工程定义拦截器如下:

package com.xuecheng.framework.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

/** Feign拦截器
 * @author Administrator
 * @version 1.0
 **/
public class FeignClientInterceptor implements RequestInterceptor {


    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(requestAttributes!=null){
            HttpServletRequest request = requestAttributes.getRequest();
            //取出当前请求的header,找到jwt令牌
            Enumeration<String> headerNames = request.getHeaderNames();
            if(headerNames!=null){
                while (headerNames.hasMoreElements()){
                    String headerName = headerNames.nextElement();
                    String headerValue = request.getHeader(headerName);
                    // 将header向下传递
                    requestTemplate.header(headerName,headerValue);

                }
            }
        }



    }
}

4.2.2 使用Feign拦截器

本例子中课程管理调用cms需要携带jwt,所以需要在课程管理中定义Feign拦截器bean,在启动类中定义bean如下:

@Bean
public FeignClientInterceptor feignClientInterceptor() {
	return new FeignClientInterceptor();
}

4.2.3 测试

执行课程发布,提示发布成功。

posted @ 2020-09-12 08:14  Artwalker  阅读(329)  评论(0编辑  收藏  举报
Live2D