用户认证

用户认证流程分析

用户认证流程如下:

业务流程说明如下:

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令牌,完成授权,拥有权限的方法正常执行,没有权限的方法将拒绝访问。

查询用户接口

Api接口

用户中心对外提供如下接口:
1、响应数据类型

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

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

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

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

DAO

添加XcUser、XcCompantUser两个表的Dao

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

Service

@Service
public class UserService {
    @Autowired
    private XcUserRepository xcUserRepository;
    //根据用户账号查询用户信息
    public XcUser findXcUserByUsername(String username){
        return xcUserRepository.findXcUserByUsername(username);
    }
    //根据账号查询用户的信息,返回用户扩展信息
    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 userId = xcUserExt.getId();
        //查询用户所属公司
        XcCompanyUser xcCompanyUser = xcCompanyUserRepository.findXcCompanyUserByUserId(userId);
        if(xcCompanyUser!=null){
            String companyId = xcCompanyUser.getCompanyId();
            xcUserExt.setCompanyId(companyId);
        }
        return xcUserExt;
    }
}

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

调用查询用户接口

创建client

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

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

UserDetailsServiceImpl

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

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

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @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){
            //返回NULL表示用户不存在,Spring Security会抛出异常
            return null;
        }
        //从数据库查询用户正确的密码,Spring Security会去比对输入密码的正确性
        String password = userext.getPassword();
        String user_permission_string = "";
        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());
        return userDetails;
    }
}

BCryptPasswordEncoder

早期使用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

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

前端显示当前用户

需求分析

用户登录成功在页头显示当前登录的用户名称。

数据流程如下图:

1、用户请求认证服务,登录成功。
2、用户登录成功,认证服务向cookie写入身份令牌,向redis写入user_token(身份令牌及授权jwt授权令牌)
3、客户端携带cookie中的身份令牌请求认证服务获取jwt令牌。
4、客户端解析jwt令牌,并将解析的用户信息存储到sessionStorage中。
jwt令牌中包括了用户的基本信息,客户端解析jwt令牌即可获取用户信息。
5、客户端从sessionStorage中读取用户信息,并在页头显示。

jwt 查询接口

需求分析

认证服务对外提供jwt查询接口,流程如下:

1 、客户端携带cookie中的身份令牌请求认证服务获取jwt
2、认证服务根据身份令牌从redis中查询jwt令牌并返回给客户端。

API

在认证模块定义 jwt查询接口:

@Api(value = "jwt查询接口",description = "客户端查询jwt令牌内容")
public interface AuthControllerApi {
    @ApiOperation("查询userjwt令牌")
    public JwtResult userjwt();

Service

在AuthService中定义方法如下:

//从redis查询令牌
    public AuthToken getUserToken(String token){
        String userToken = "user_token:"+token;
        String userTokenString = stringRedisTemplate.opsForValue().get(userToken);
        if(userToken!=null){
            AuthToken authToken = null;
            try {
                authToken = JSON.parseObject(userTokenString, AuthToken.class);
            } catch (Exception e) {
                LOGGER.error("getUserToken from redis and execute JSON.parseObject error
{}",e.getMessage());
                e.printStackTrace();
            }
            return authToken;
        }
        return null;
    }

Controller

 @Override
    @GetMapping("/userjwt")
    public JwtResult userjwt() {
        //获取cookie中的令牌
String access_token = getTokenFormCookie();
        //根据令牌从redis查询jwt
        AuthToken authToken = authService.getUserToken(access_token);
        if(authToken == null){
            return new JwtResult(CommonCode.FAIL,null);
        }
        return new JwtResult(CommonCode.SUCCESS,authToken.getJwt_token());
    }
    //从cookie中读取访问令牌
    private String getTokenFormCookie(){
        Map<String, String> cookieMap = CookieUtil.readCookie(request, "uid");
        String access_token = cookieMap.get("uid");
        return access_token;
    }

测试

使用postman测试
1、请求 /auth/userlogin

观察cookie是否已存入用户身份令牌。

2、get请求jwt

用户退出

需求分析

操作流程如下:
1、用户点击退出,弹出退出确认窗口,点击确定

2、退出成功

用户退出要以下动作:
1、删除redis中的token
2、删除cookie中的token

API

认证服务对外提供退出接口。

@ApiOperation("退出")
public ResponseResult logout();

服务端

认证服务提供退出接口。

Service

//从redis中删除令牌
public boolean delToken(String access_token){
    String name = "user_token:" + access_token;
    stringRedisTemplate.delete(name);
    return true;
}

Controller

//退出
    @Override
    @PostMapping("/userlogout")
    public ResponseResult logout() {
        //取出身份令牌
        String uid = getTokenFormCookie();
        //删除redis中token
        authService.delToken(uid);
        //清除cookie
        clearCookie(uid);
        return new ResponseResult(CommonCode.SUCCESS);
    }
    //清除cookie
    private void clearCookie(String token){
        CookieUtil.addCookie(response, cookieDomain, "/", "uid", token, 0, false);
    }

退出URL放行

认证服务默认都要校验用户的身份信息,这里需要将退出url放行。
在WebSecurityConfig类中重写 configure(WebSecurity web)方法,如下:

@Override    
    public void configure(WebSecurity web) throws Exception {
         web.ignoring().antMatchers("/userlogin","/userlogout");
    }

Zuul 网关

Zuul 介绍

什么是Zuul?
Spring Cloud Zuul是整合Netflix公司的Zuul开源项目实现的微服务网关,它实现了请求路由、负载均衡、校验过
虑等 功能。

什么是网关?
服务网关是在微服务前边设置一道屏障,请求先到服务网关,网关会对请求进行过虑、校验、路由等处理。有了服
务网关可以提高微服务的安全性,网关校验请求的合法性,请求不合法将被拦截,拒绝访问。
Zuul与Nginx怎么配合使用?
Zuul与Nginx在实际项目中需要配合使用,如下图,Nginx的作用是反向代理、负载均衡,Zuul的作用是保障微服
务的安全访问,拦截微服务请求,校验合法性及负载均衡。

路由配置

需求分析

Zuul网关具有代理的功能,根据请求的url转发到微服务,如下图:

客户端请求网关/api/learning,通过路由转发到/learning
客户端请求网关/api/course,通过路由转发到/course

路由配置

在appcation.yml中配置:

zuul:
  routes:
    manage‐course:  #路由名称,名称任意,保持所有路由名称唯一
      path: /course/** 
      serviceId: xc‐service‐manage‐course #指定服务id,从Eureka中找到服务的ip和端口
      #url: http://localhost:31200 #也可指定url
      strip‐prefix: false #true:代理转发时去掉前缀,false:代理转发时不去掉前缀
      sensitiveHeaders: #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取消默认的黑名
单,如果设置了具体的头信息则不会传到下游服务
#   ignoredHeaders: Authorization

serviceId:推荐使用serviceId,zuul会从Eureka中找到服务id对应的ip和端口。
strip-prefix: false #true:代理转发时去掉前缀,false:代理转发时不去掉前缀,例如,为true请
求/course/coursebase/get/..,代理转发到/coursebase/get/,如果为false则代理转发到/course/coursebase/get

sensitiveHeaders :敏感头设置,默认会过虑掉cookie,这里设置为空表示不过虑
ignoredHeaders:可以设置过虑的头信息,默认为空表示不过虑任何头

完整的路由配置

zuul:
  routes:
    xc‐service‐learning:  #路由名称,名称任意,保持所有路由名称唯一
      path: /learning/**
      serviceId: xc‐service‐learning #指定服务id,从Eureka中找到服务的ip和端口
      strip‐prefix: false
      sensitiveHeaders:
    manage‐course:
      path: /course/**
      serviceId: xc‐service‐manage‐course
      strip‐prefix: false
      sensitiveHeaders:
    manage‐cms:
      path: /cms/**
      serviceId: xc‐service‐manage‐cms
      strip‐prefix: false
      sensitiveHeaders:
    manage‐sys:
      path: /sys/**
      serviceId: xc‐service‐manage‐cms
      strip‐prefix: false
sensitiveHeaders:
    service‐ucenter:
      path: /ucenter/**
      serviceId: xc‐service‐ucenter
      sensitiveHeaders:
      strip‐prefix: false
    xc‐service‐manage‐order:
       path: /order/**
       serviceId: xc‐service‐manage‐order
       sensitiveHeaders:
       strip‐prefix: false

过滤器

Zuul的核心就是过虑器,通过过虑器实现请求过虑,身份校验等。

ZuulFilter

自定义过虑器需要继承 ZuulFilter,ZuulFilter是一个抽象类,需要覆盖它的四个方法,如下:
1、 shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true表示要执行此过虑器,否则不执
行。 2、 run:过滤器的业务逻辑。 3、 filterType:返回字符串代表过滤器的类型,如下 pre:请求在被路由之前
执行 routing:在路由请求时调用 post:在routing和errror过滤器之后调用 error:处理请求时发生错误调用
4、 filterOrder:此方法返回整型数值,通过此数值来定义过滤器的执行顺序,数字越小优先级越高。

测试

过虑所有请求,判断头部信息是否有Authorization,如果没有则拒绝访问,否则转发到微服务。
定义过虑器,使用@Component标识为bean。

@Component
public class LoginFilterTest extends ZuulFilter {
    private static final Logger LOG = LoggerFactory.getLogger(LoginFilterTest.class);
    @Override
    public String filterType() {
        return "pre";
    }
    @Override
    public int filterOrder() {
        return 2;//int值来定义过滤器的执行顺序,数值越小优先级越高
    }
    @Override
    public boolean shouldFilter() {// 该过滤器需要执行
        return true;
 }
    @Override
    public Object run() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse response = requestContext.getResponse();
        HttpServletRequest request = requestContext.getRequest();
        //取出头部信息Authorization
        String authorization = request.getHeader("Authorization");
        if(StringUtils.isEmpty(authorization)){
            requestContext.setSendZuulResponse(false);// 拒绝访问
         requestContext.setResponseStatusCode(200);// 设置响应状态码    
         ResponseResult unauthenticated = new ResponseResult(CommonCode.UNAUTHENTICATED);    
         String jsonString = JSON.toJSONString(unauthenticated);    
         requestContext.setResponseBody(jsonString);    
         requestContext.getResponse().setContentType("application/json;charset=UTF‐8");    
            return null;
        }
        return null;
    }
}

测试:
请求:http://localhost:50201/api/course/coursebase/get/4028e581617f945f01617f9dabc40000查询课程信息

学成在线(第17天)用户认证 Zuul的更多相关文章

  1. 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_03-用户认证技术方案-Oauth2协议

    2.2 Oauth2认证 2.2.1 Oauth2认证流程 第三方认证技术方案最主要是解决认证协议的通用标准 问题,因为要实现 跨系统认证,各系统之间要遵循一定的 接口协议. OAUTH协议为用户资源 ...

  2. 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_01-用户认证需求分析

    1.1 用户认证与授权 截至目前,项目已经完成了在线学习功能,用户通过在线学习页面点播视频进行学习.如何去记录学生的学习过程 呢?要想掌握学生的学习情况就需要知道用户的身份信息,记录哪个用户在什么时间 ...

  3. 微服务项目开发学成在线_day02 CMS前端开发

    1 Vue.js与Webpack研究 开发版的浏览器:https://www.google.cn/intl/zh-CN/chrome/dev/ 前端的开发框架:微服务项目开发学成在线_Vue.js与W ...

  4. 微服务项目开发学成在线_day01_CMS服务端开发

    05-CMS需求分析-什么是CMS 什么是CMS?CMS (Content Management System)即内容管理系统,不同的项目对CMS的定位不同.CMS有哪些类型? 每个公司对每个项目的C ...

  5. 阶段5 3.微服务项目【学成在线】_day04 页面静态化_16-页面静态化-模板管理-模板制作

    这是轮播图的原始文件 运行门户需要把 nginx启动起来 单独运行轮播图.把里面的css的引用都加上网址的url 这就是单独访问到的轮播图的效果 轮播图模板的地址: 阶段5 3.微服务项目[学成在线] ...

  6. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_16-CMS前端工程创建-导入系统管理前端工程

    提供了基于脚手架封装好的前端工程 H:\BaiDu\黑马传智JavaEE57期 2019最新基础+就业+在职加薪\阶段5 3.微服务项目[学成在线]·\day02 CMS前端开发\资料\xc-ui-p ...

  7. 阶段5 3.微服务项目【学成在线】_day17 用户认证 Zuul_01-用户认证-用户认证流程分析

    1 用户认证 1.1 用户认证流程分析 用户认证流程如下: 访问下面的资源需要携带身份令牌和jwt令牌,客户端可以通过身份认证的令牌从服务端拿到长令牌, 一会要实现认证服务请求用户中心从数据库内来查询 ...

  8. 阶段5 3.微服务项目【学成在线】_day18 用户授权_17-细粒度授权-获取当前用户信息

    3.4.1需求分析 要想实现只查询自己的课程信息则需要获取当前用户所属的企业id. 1.认证服务在用户认证通过将用户所属公司id等信息存储到jwt令牌中. 2.用户请求到达资源服务后,资源服务需要取出 ...

  9. 阶段5 3.微服务项目【学成在线】_day18 用户授权_07-动态查询用户权限-权限数据模型

    3 动态查询用户权限 3.1 需求分析 截至目前在测试授权时使用的权限数据是静态数据,正常情况的流程是: 1.管理员给用户分配权限,权限数据写到数据库中. 2.认证服务在进行用户认证时从数据库读取用户 ...

随机推荐

  1. Perl 笔记

    目录 Perl 学习 常用记录 基础 1. 运行perl 2. 字符串 3. 变量 4. 条件 5. 循环 6. 运算符 7. 时间日期 8. 子程序(函数) 9. 引用 10. 格式化输出 11. ...

  2. Hibernate学习过程出现的问题

    1  核心配置文件hibernate.cfg.xml添加了约束但是无法自动获取属性值 解决方案:手动将DTD文件导入 步骤:倒开Eclipse,找到[window]->[preference]- ...

  3. Docker 安装 Kibana

    使用和 elasticsearch 相同版本镜像 7.4.1 (不一样可能会出现问题) 1.下载Kibana镜像  # 下载Kibana镜像 docker pull kibana: # 查看镜像 do ...

  4. 杭电 1114 Piggy-Bank 完全背包问题

    Piggy-Bank Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total ...

  5. kafka start bat

    start D:\Homes\kafka_2.12-2.3.0\bin\windows\kafka-server-start.bat D:\Homes\kafka_2.12-2.3.0\config\ ...

  6. re.compile匹配

    import re string = '<h4 class="title">愤怒的葡萄</h4>' pattern = '<h4 class=&quo ...

  7. Django 学习之Rest Framework 视图集与Routers与扩展功能

    一.视图集使用 使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中: list() 提供一组数据 retrieve() 提供单个数据 create() 创建数据 update() 保存数 ...

  8. django 模版标签笔记

    一.模板变量笔记:1.在模版中使用变量,需要将变量放到‘{{}}’中.'{{ 变量 }}'2.如果想访问对象的属性,可以通过'对象.属性名'的方式访问3.如果想要访问一个字典的key对应的value, ...

  9. CSP2019 滚粗记

    目录 CSP 2019 游记 DAY 0 DAY 1 DAY 2 CSP总结 自测之后 CSP 2019 游记 坐标:GD,GZ 人物:hyf 组别:J和S 任务:划水 目标:划水 任务奖励:退役证书 ...

  10. vue element 时间选择器设置禁用日期

    在 el-date-picker 组件中有一个 picker-options 属性 disabledDate 可以设置日期的可选范围 <el-date-picker v-model=" ...