微服务[学成在线] day17:基于Zuul网关实现路由转发、过滤器

本文详细介绍了基于Zuul实现用户认证的流程,包括用户登录、前端显示用户信息、用户退出以及Zuul网关的路由配置和过滤器。通过Zuul,实现了身份校验、路由转发等功能,确保了微服务的安全性和访问控制。
摘要由CSDN通过智能技术生成

😎 知识点概览

为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。

本章节为【学成在线】项目的 day17 的内容

  • 构建用户中心服务,并基于 Spring Security Oauth2 以及 jwt 令牌实现用户认证的完整流程。
  • 完成门户网站的用户登入、登出接口、前端页面的开发以及调试。
  • 基于 Zuul 构建网关服务,以及使用 Zuul 网关实现基本的路由转发、过滤器、身份校验等功能。

目录

内容会比较多,小伙伴们可以根据目录进行按需查阅。

一、用户认证

0x01 用户认证流程分析

用户认证流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QeiDrDHT-1595567384585)(https://qnoss.codeyee.com/20200704_MTc=/image1.png)]

业务流程说明如下:

1、客户端请求认证服务进行认证。

2、认证服务认证通过向浏览器 cookie 写入 token (身份令牌)

认证服务请求用户中心查询用户信息。

认证服务请求 Spring Security 申请令牌。

认证服务将 token (身份令牌)和 jwt 令牌存储至 redis 中。

认证服务向cookie写入 token (身份令牌)。

3**、前端携带token请求认证服务获取**jwt令牌

前端获取到 jwt 令牌并存储在 sessionStorage

前端从jwt令牌中解析中用户信息并显示在页面。

前端如何解析?还是认证服务返回明文数据

4**、前端携带cookie中的token身份令牌及jwt令牌访问资源服务**

前端请求资源服务需要携带两个token,一个是cookie中的身份令牌,一个是http header中的jwt令牌

前端请求资源服务前在http header上添加jwt请求资源

5、网关校验 token的合法性

用户请求必须携带 token 身份令牌和jwt令牌

网关校验redis中 token 是否合法,已过期则要求用户重新登录

6、资源服务校验jwt的合法性并完成授权

资源服务校验jwt令牌,完成授权,拥有权限的方法正常执行,没有权限的方法将拒绝访问。

0x02 认证服务查询数据库

需求分析

  • 认证服务根据数据库中的用户信息去校验用户的身份,即校验账号和密码是否匹配。

  • 认证服务不直接连接数据库,而是通过用户中心服务去查询用户中心数据库。

image-20200527084040941

完整的流程图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQ7m6VrN-1595567384594)(https://qnoss.codeyee.com/20200704_MTc=/image3.png)]

搭建环境

1、创建用户中心数据库

用户中心负责用户管理,包括:用户信息管理、角色管理、权限管理等。

创建 xc_user 数据库(MySQL)

导入 xc_user.sql (已导入不用重复导入)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-017D2qLJ-1595567384600)(https://qnoss.codeyee.com/20200704_MTc=/image4.png)]

2、创建用户中心工程

导入“资料”-》xc-service-ucenter.zip

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5bvUFkv-1595567384604)(https://qnoss.codeyee.com/20200704_MTc=/image5.png)]

完成用户中心根据账号查询用户信息接口功能。

查询用户接口开发

1、Api接口

用户中心对外提供如下接口

1)响应数据类型

此接口将来被用来查询用户信息及用户权限信息,所以这里定义扩展类型

package com.xuecheng.framework.domain.ucenter.response.ext;
import com.xuecheng.framework.domain.ucenter.XcMenu;
import com.xuecheng.framework.domain.ucenter.XcUser;
import lombok.Data;
import lombok.ToString;

import java.util.List;

@Data
@ToString
public class XcUserExt extends XcUser {
   
    //权限信息
    private List<XcMenu> permissions;
    //企业信息
    private String companyId;
}

2)根据账号查询用户信息

package com.xuecheng.api.ucenter;

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

@Api(value = "用户中心",description = "用户中心管理")
public interface UcenterControllerApi {
   
    
    @ApiOperation("获取用户信息")
    public XcUserExt getUserext(String username);
}

2、DAO

添加 XcUserXcCompantUser 两个表的Dao ,对于一些简单的sql操作,我们使用 Spring Data JPA 实现

public interface XcUserRepository extends JpaRepository<XcUser, String> {
   
    XcUser findXcUserByUsername(String username);
} 

public interface XcCompanyUserRepository extends JpaRepository<XcCompanyUser,String> {
    //根据用户id查询所属企业id
    XcCompanyUser findByUserId(String userId);
}
3、Service

@Service
public class UserServiceImpl implements UserService {
   

    //对xc_user表的相关操作
    @Autowired
    XcUserRepository xcUserRepository;

    //对xc_company_user表的相关操作
    @Autowired
    XcCompanyUserRepository xcCompanyUserRepository;

    /**
     * 根据用户名查询用户信息的实现
     * @param username
     * @return
     */
    @Override
    public XcUser findXcUserByUsername(String username) {
   
        return xcUserRepository.findXcUserByUsername(username);
    }

    /**
     * 根据用户名获取用户权限的实现
     * @param username 用户名
     * @return
     */
    @Override
    public XcUserExt getUserExt(String username) {
   
        //查询用户信息
        XcUser xcUser = this.findXcUserByUsername(username);
        if(xcUser ==null) return null;

        XcUserExt xcUserExt = new XcUserExt();
        BeanUtils.copyProperties(xcUser,xcUserExt);

        //根据用户id查询用所属公司
        String xcUserId = xcUser.getId();
        XcCompanyUser xcCompanyUser = xcCompanyUserRepository.findByUserId(xcUserId);
        if(xcCompanyUser!=null){
   
            String companyId = xcCompanyUser.getCompanyId();
            xcUserExt.setCompanyId(companyId);
        }

        //返回XcUserExt对象
        return xcUserExt;
    }
}

4、Controller
@RestController
@RequestMapping("/ucenter")
public class UcenterController implements UcenterControllerApi {
   
    @Autowired
    UserService userService;
    @Override
    @GetMapping("/getuserext")
    public XcUserExt getUserext(@RequestParam("username") String username) {
   
        XcUserExt xcUser = userService.getUserExt(username);
        return xcUser;
    }
}
5、可能出现的一些问题

如果 ucenter 服务出现接口需要认证才能访问的情况,考虑可能是继承了 model 工程的 oauth2 依赖导致开启了认证拦截。

解决方案:在 model 工程下的 oauth2 依赖加上 <optional>true</optional> 标签,该标签可以防止本工程下的依赖包传递到其他工程。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <optional>true</optional>
</dependency>
6、测试

使用 Swagger-uipostman 测试用户信息查询接口

GET http://localhost:40300/ucenter/getuserext

参数为 username

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFnI31Zd-1595567384606)(https://qnoss.codeyee.com/20200704_MTc=/image6.png)]

7、思考一些问题

在上述测试过程中,通过 GET 请求调用 http://localhost:40300/ucenter/getuserext 接口可以获取到一个用户的详细信息,但是考虑到用户数据的安全问题,这个接口不应该直接暴露给普通的用户,只适合服务间的调用,并需要经过授权的服务才可以调用。

答:后期配置微服务间认证后可以解决上述的问题。

调用查询用户的接口

1、创建 client

认证服务需要远程调用用户中心服务查询用户,在 认证服务 中创建Feign客户端

@FeignClient(value = XcServiceList.XC_SERVICE_UCENTER)
public interface UserClient {
   
    @GetMapping("/ucenter/getuserext")
    public XcUserExt getUserext(@RequestParam("username") String username)
}
2、UserDetailsService

认证服务调用 spring security 接口申请令牌,spring security 接口会调用 UserDetailsServiceImpl 从数据库查询用户,如果查询不到则返回 NULL,表示不存在;在UserDetailsServiceImpl 中将正确的密码返回, spring security 会自动去比对输入密码的正确性。

修改 UserDetailsServiceImpl 的 loadUserByUsername 方法,调用 Ucenter服务的查询用户接口

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
   

    @Autowired
    ClientDetailsService clientDetailsService;

    //用户中心服务客户端
    @Autowired
    UserClient userClient;

    @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;
        }

        //请求ucenter查询用户
        XcUserExt userext = userClient.getUserext(username);
        if(userext == null) return null; //如果获取到的用信息为空,则返回null,spring security则会抛出异常

        //设置用户的认证和权限信息
        userext.setUsername("itcast");
        userext.setPassword(new BCryptPasswordEncoder().encode("123"));
        userext.setPermissions(new ArrayList<XcMenu>());  //这里授权部分还没完成,所以先填写静态的
        if(userext == null){
   
            return null;
        }

        //从数据库查询用户正确的密码,Spring Security会去比对输入密码的正确性
        String password = userext.getPassword();
        String user_permission_string = "";

        //设置用户信息到userDetails对象
        UserJwt userDetails = new UserJwt(
                username,
                password,
                AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));
        //用户id
        userDetails.setId(userext.getId());
        //用户名称
        userDetails.setName(userext.getName());
        //用户头像
        userDetails.setUserpic(userext.getUserpic());
        //用户所属企业id
        userDetails.setCompanyId(userext.getCompanyId());

        //返回用信息给到Spring Security进行处理
        return userDetails;
    }
}
3、BCryptPaswordEncoder

早期使用md5对密码进行编码,每次算出的md5值都一样,这样非常不安全,Spring Security推荐使用
BCryptPasswordEncoder对密码加随机盐,每次的Hash值都不一样,安全性高 。

1)BCryptPasswordEncoder测试程序如下

@Test
public void testPasswrodEncoder(){
   
    String password = "111111";
    PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    for(int i=0;i<10;i++) {
   
        //每个计算出的Hash值都不一样
        String hashPass = passwordEncoder.encode(password);
        System.out.println(hashPass);
        //虽然每次计算的密码Hash值不一样但是校验是通过的
        boolean f = passwordEncoder.matches(password, hashPass);
        System.out.println(f);
    }
}

2)在 AuthorizationServerConfig 配置类中配置 BCryptPasswordEncoder

原教程中已经在 WebSecurityConfig 中进行了配置,这个在哪里配置都无所谓,本质上都是向spring注入一个bean

//采用bcrypt对密码进行Hash
@Bean
public PasswordEncoder passwordEncoder() {
   
	return new BCryptPasswordEncoder();
}

3)测试

请求 http://localhost:40400/auth/userlogin,输入正常的账号和密码进行测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tui8jCcF-1595567384607)(https://qnoss.codeyee.com/20200704_MTc=/image7.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值