微服务+vue架构+oauth2+权限系统的实现

Posted Spring(小雨点)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务+vue架构+oauth2+权限系统的实现相关的知识,希望对你有一定的参考价值。

思路: 1.登录认证oauth/token拦截器 2找到user信息 3.利用user字段得到token,返回赋值给session 4请求权限信息,网管拦截 ,用token去经过网关解析成id 5、跳转微服务getinfo,获取id查询用户权限角色。
开发技巧:代码一点一点的加,一点一点的测试,如果错了可以返回上一步。有时候加多了,他会报不相干的错误,比如因为加了dao层进行了跨域。
网关不过滤就重启

1.网关配置:拦截器实现,要放行oauth/token,所有请求接口经过网关ip转发。
网关白名单放行/yz/oauth/token,也经过网关拦截过滤器, (网关都要加前缀,好进行分发),其他请求都要经过网关拦截器验证token。
第一次请求自带的token参数代表了clientid 和clientsecret

网关拦截器,把token 存在request中

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) 
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StrUtil.isEmpty(token)) 
            return chain.filter(exchange);
        
        if (token.startsWith("bearer")) 
            try 
                //从token中解析用户信息并设置到Header中去
                System.out.println("从token中解析用户信息并设置到Header中去");
                String realToken = token.replace("bearer ", "");
                JWSObject jwsObject = JWSObject.parse(realToken);
                String userStr = jwsObject.getPayload().toString();
                System.out.println(userStr);
                //信息加入到head里面。
                ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
                try 
                    userStr= URLEncoder.encode(userStr, "UTF-8");
                catch (Exception e)
                    e.printStackTrace();
                
                request = exchange.getRequest().mutate().header("enuser", userStr).build();
                exchange = exchange.mutate().request(request).build();
             catch (ParseException e) 
                e.printStackTrace();
            
        
        return chain.filter(exchange);
    

大坑,ip不要用前端映射,其实是需要重启。有缓存。

网关搭建

2.配置认证服务器
任何接口资源/users/me都要微服务这里放行,不带yz网关前缀,因为是网关转过来的。

认证服务器请求拦截器,每个服务都有
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.authorizeRequests()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
                // @link https://gitee.com/xiaoym/knife4j/issues/I1Q5X6 (接口文档knife4j需要放行的规则)
                .antMatchers("/webjars/**","/doc.html","/swagger-resources/**","/v2/api-docs").permitAll()
                .antMatchers("/rsa/publicKey","/decode" ,"/users/me","/sys/menus","/sys/menus_cbs","/redirect").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    

oauth2配置客户端
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
        System.out.println("[2]----加载认证服务配置");
        clients.inMemory()
             .withClient("client-api")
                .secret(passwordEncoder.encode("123456"))
                .scopes("all")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(60 * 60 * 5)//token 有效期15分钟
                .refreshTokenValiditySeconds(60 * 60 * 10)//有效期内可以刷新token
                .and()
             .withClient("client-jump")
                .secret(passwordEncoder.encode("123456"))
                .scopes("all")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(60 * 15)//token 有效期15分钟
                .refreshTokenValiditySeconds(60 * 30)//有效期内可以刷新token
                .and()
             .withClient("client-app")
                .secret(passwordEncoder.encode("123456"))
                .scopes("all")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(60 * 15)//token 有效期15分钟
                .refreshTokenValiditySeconds(60 * 30);//有效期内可以刷新token
    
oauth2配令牌服务,产生token,需要userDetailsService作为生成token的参数
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList();
        delegates.add(tokenEnhancer());
        delegates.add(accessTokenConverter());
        enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService) //配置加载用户信息的服务,对比的用的userDetailsService查找信息
                .accessTokenConverter(accessTokenConverter())
                .reuseRefreshTokens(false)
                .tokenEnhancer(enhancerChain);
    
token生成的字段
    public TokenEnhancer tokenEnhancer() 
        return (accessToken, authentication) -> 
            Map<String, Object> additionalInfo = CollectionUtil.newHashMap();
            SecurityUser OAuthUserDetails = (SecurityUser) authentication.getPrincipal();
//            additionalInfo.put("userId", OAuthUserDetails.getId());
            additionalInfo.put("oilfield", OAuthUserDetails.getOilfield());
            additionalInfo.put("dataSource", OAuthUserDetails.getDataSource());
            additionalInfo.put("unitid", OAuthUserDetails.getUnitid());
            additionalInfo.put("user_Id", OAuthUserDetails.getUser_Id());
            additionalInfo.put("truename", OAuthUserDetails.getRealname());
            additionalInfo.put("unitname", OAuthUserDetails.getUnitname());
            additionalInfo.put("username", OAuthUserDetails.getUsername());
//            additionalInfo.put("id", OAuthUserDetails.getId());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        ;
    

3.前端 js认证请求post

 loginForm: 
          username: 'admin',
          password: '123456',
          grant_type:'passwrod',//密码认证模式,passwrod写错了,错一个字母,就要排查好一会错误bug
          client_secret:'123456',
          client_id:'client-app'
        ,


 axios.post(`/api/yz/oauth/token?username=$loginForm.username&password=$loginForm.password&grant_type=$loginForm.grant_type`,
            loginForm,  headers:  Authorization: 'Basic Y2xpZW50LWFwcDoxMjM0NTY='  
          ).then((res) => 
            debugger;
               if (res.msg === "success") 
            //使用session存储token值
                 sessionStorage.setItem('cloud-ida-token',res.data);
            //cookie中保存前端登录状态
                 setToken();
                
               resolve(res);
            ).finally(() => 
              this.loading = false
            )
            setTimeout(() => 
              // console.log(this.$route.params.from)
              //  window.location.replace(this.$route.params.from || '/jxzy02')
            )
          ).finally(() => 
            //this.loadin
          );

返回参数跳转
     handleLogin() 
        this.$refs.loginForm.validate(valid => 
          if (valid) 
            this.loading = true
            this.$store.dispatch('Login', this.loginForm).then(data => 
              console.log('获取到的data: '+data.msg);
              this.loading = false
              if ("success" === data.msg) 
                this.$router.push(path: '/')
               else 
                this.$message.error("账号/密码错误");
              
            ).catch(() => 
              this.loading = false
            )
           else 
            return false
          
        )
      
    
  


4.认证后台,对比账号,先用username查询,后面用userid查询

 `    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        System.out.println("[4]---loadUserByUsername");
//        List<UserDTO> findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
        UserDTO userDTO=new UserDTO();//实体类
        userDTO.setUser_name(username);
        List<UserDTO> findUserList = dao.find("loadUserByUsername",userDTO);//查询
        if (CollUtil.isEmpty(findUserList)) 
            throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
        
        userDTO = findUserList.get(0);
        userDTO.setStatus(1);
        userDTO.setRoles(CollUtil.toList("ADMIN"));
        //数据库中的密码(需前端传递过来md5加密的密码)
      userDTO.setPassword(passwordEncoder.encode(userDTO.getPassword().toLowerCase()));

        SecurityUser securityUser = new SecurityUser(userDTO);
        if (!securityUser.isEnabled()) 
            throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
         else if (!securityUser.isAccountNonLocked()) 
            throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
         else if (!securityUser.isAccountNonExpired()) 
            throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
         else if (!securityUser.isCredentialsNonExpired()) 
            throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
        
        return securityUser;
    
`



跳转到配置里的token 生成,把需要的字段添加进去,然后在第二次请求token解析的时候可以拿到字段

   @Bean
    public TokenEnhancer tokenEnhancer() 
        return (accessToken, authentication) -> 
            Map<String, Object> additionalInfo = CollectionUtil.newHashMap();
            SecurityUser OAuthUserDetails = (SecurityUser) authentication.getPrincipal();
//            additionalInfo.put("userId", OAuthUserDetails.getId());
            additionalInfo.put("oilfield", OAuthUserDetails.getOilfield());
            additionalInfo.put("dataSource", OAuthUserDetails.getDataSource());
            additionalInfo.put("unitid", OAuthUserDetails.getUnitid());
            additionalInfo.put("user_Id", OAuthUserDetails.getUser_Id());
            additionalInfo.put("truename", OAuthUserDetails.getRealname());
            additionalInfo.put("unitname", OAuthUserDetails.getUnitname());
            additionalInfo.put("username", OAuthUserDetails.getUsername());
//            additionalInfo.put("id", OAuthUserDetails.getId());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        ;
    

5.返回js处理,保存token到session下次发送请求带上,设置cookie已经登录的状态。

token的保存格式要和图中一样。中间加个空格。。

  // 登录返回处理
    Login(commit, state, loginForm) 
      return new Promise((resolve, reject) => 
          this.loading = true
          axios.post(`/api/oauth/token?username=$loginForm.username&password=$loginForm.password&grant_type=$loginForm.grant_type&client_secret=$loginForm.client_secret&client_id=$loginForm.client_id`,
            loginForm,  headers:  Authorization: 'Basic Y2xpZW50LWFwcDoxMjM0NTY='  
          ).then((r) => 
            debugger;
            r=r.data;
               if (r.msg === "操作成功") 
            //使用session存储token值,,,,,,有一定的格式
                 sessionStorage.setItem('cloud-ida-token',r.data.access_token);
            //cookie中保存前端登录状态
                 setToken();
                
               resolve(r);
            ).finally(() => 
              this.loading = false
            )
            setTimeout(() => 
              // console.log(this.$route.params.from)
              //  window.location.replace(this.$route.params.from || '/jxzy02')
            )
          ).finally(() => 
            //this.loadin
          );

cookie在登出的时候得清除掉,否则自动去getinfo,,, setToken()方法

import Cookies from 'js-cookie'
const LoginKey = 'hasLogin'//设置coolie已经登录
export function getToken() 
  return Cookies.get(LoginKey);

export function setToken() 
  return Cookies.set(LoginKey, "1")

export function removeToken() 
  return Cookies.remove(LoginKey)


每次请求的axios处理

import axios from 'axios'
import Message, MessageBox from 'element-ui'
import getToken from '@/utils/auth'
import store from '../store'
// 创建axios实例
const service = axios.create(
  baseURL: process.env.BASE_URL, // api的base_url
  timeout: 15000                  // 请求超时时间2
)
// request拦截器
service.interceptors.request.use(config => 
  config.headers.Authorization=sessionStorage.getItem('cloud-ida-token');//每次请求都要加token
  return config
, error => 
  // Do something with request error
  console.error(error) // for debug
  Promise.reject(error)
)
// respone拦截器
service.interceptors.response.use(
  response => 
    const res = response.data;
    if (res.code == "200") 
      return res
    

    if (res.code == '10002') 
      Message(
        showClose: true,
        message: res.msg,
        type: 'error',
        duration: 3 * 1000,
        onClose: () => 
          store.dispatch('FedLogOut').then(() => 
            location.reload()// 为了重新实例化vue-router对象 避免bug
          )
        
      );
      return Promise.reject(res.msg)
    else
      Message(
        message: res.msg,
        type: 'error',
        duration: 3 * 1000
      )
      return res
    
  ,
  error => 
    console.error('err' + error)// for debug
    Message(
      message: error.message,
      type: 'error',
      duration: 3 * 1000
    )
    return Promise.reject(error)
  
)
export default service

7前端配置
跨域设置,认证完经过网关跳转不了微服务可能是跨域问题,,,也可能是接口输错了user/mehhhe和users、me、()dakeng大坑
8,getinfo带着token去网关验证得到id,放在request的header里,跳转到微服务中取出,然后获取信息。

先建表,bean,mapper接口和xml,service,controull层。一步步建立,学习架子里的语法。
springboot找不到dao
跨域问题

编译错误

看返回,返回失败,跳转到login

import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css' // Progress 进度条样式
import getToken from '@/utils/auth' // 验权
const whiteList = ['/login', '/404'] //白名单,不需要登录的路由
router.beforeEach((to, from, next) => 
  NProgress.start()
  if (getToken()) 
    //如果已经登录

    if (to.path === '/login') 
      next(path: '/')
      NProgress.done() // 结束Progress
     else if (!store.getters.role) 
      //跳转后台
      store.dispatch('GetInfo').then(() => 
        next(...to)
      )
     else 
      next()
    
   else if (whiteList.indexOf(to.path) !== -1) 
    //如果前往的路径是白名单内的,就可以直接前往
    next()
   else 
    //如果路径不是白名单内的,而且又没有登录,就跳转登录页面
    next('/login')
    NProgress.done() // 结束Progress
  
)
router.afterEach(() => 
  NProgress.done() // 结束Progress
)

9.dao层,对应好mapper命名空间去查,先测试通sql语句


父依赖错误
mybatis操作
resultmap错误

没扫描到:看扫描配置文件,必须加后缀

用户表和角色表中间有用户角色关系表


角色表和权限表中间有角色和权限关系表,权限表里有父权限字段,可以当作条件

menu是小权限,他没有表,是权限表的限定,比如限定条件是父id15,返回类型是STRING,返回list一个个转成set即可。

10更新权限

 
更新权限
    @Transactional
    @Override
    public Integer batchInsert(List<RolePermission> rolepermissions) 
        RolePermission rolepermission=rolepermissions.get(0);
        rolepermissionMapper.deleteByRoleId(rolepermission.getRoleId());
        return rolepermissionMapper.batchInsert(rolepermissions);
    
 

干货 | 微服务架构下 Spring Cloud OAuth2 通用权限管理系统

点击蓝色“泥瓦匠BYSocket”,关注我哟

加个星标”,不忘文末签到哦


项目地址

Pig Microservice Architecture

  • 基于 Spring Cloud Greenwich 、Spring Security OAuth2 的RBAC权限管理系统 (Zuul 版本参考 1.x 分支)

  • 基于数据驱动视图的理念封装 Element-ui,即使没有 vue 的使用经验也能快速上手

  • 提供对常见容器化支持 Docker、Kubernetes、Rancher2 支持

  • 提供 lambda 、stream api 、webflux 的生产实践

1. 项目核心

  • 一个注解接入OAuth 2.0 认证。 借助spring security 完成企业级权限设计,杜绝重复造轮子

  • 让更多的微服务耳熟能详的名次,比如灰度发布、流量控制、动态路由等不仅存在于PPT,通过Spring Cloud 作为载体实现

  • 几行代码完成Vue 的CRUD。 运用layui、easyui 数据驱动视图的理念封装,让后端工程师无缝上手

2. 资源服务器接入

  • 一个EnablePigResourceServer 注解 即可接入oauth2 服务器,权限管理被spring security接管。相较于原生的spring security oauth2 复杂的配置,EnablePigResourceServer 完成了资源服务器的全部功能 及其整合注册中的负载均衡

 
   
   
 
  1. @EnablePigResourceServer

  2. @EnablePigFeignClients

  3. @SpringCloudApplication

  4. public class PigAdminApplication {

  5. public static void main(String[] args) {

  6. SpringApplication.run(PigAdminApplication.class, args);

  7. }


  8. }

3. 服务端动态路由

动态路由需要达到可持久化配置,动态刷新的效果。不仅要能满足从spring的配置文件properties加载路由信息,还需要从数据库加载我们的配置。另外一点是,路由信息在容器启动时就已经加载进入了内存,我们希望配置完成后,实施发布,动态刷新内存中的路由信息,达到不停机维护路由信息的效果,无论是zuul,还是spring cloud gateway 都提供了实现.通过内存 + Redis 的二级缓存策略让性能更加高效干货 | 微服务架构下 Spring Cloud OAuth2 通用权限管理系统

4. 单点登录

单点登录是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。pig 1.X 分支基于 Spring security oauth 也提供了生产实践干货 | 微服务架构下 Spring Cloud OAuth2 通用权限管理系统

5. 灰度发布

通过客户端声明的不同版本,可以通过网关进行转发到不同版本的业务微服务,实现生产不停机的灰度发布,保障服务的高可用。

 
   
   
 
  1. 在微服务的eureka客户端声明版本所属

  2. eureka:

  3. instance:

  4. metadata-map:

  5. version: v1.0

干货 | 微服务架构下 Spring Cloud OAuth2 通用权限管理系统

6. 多维度限流

zuul版本整合spring-cloud-zuul-ratelimit,gateway版本是原生扩展
目前支持的限流粒度

7. 分库分表

zuul版本.采用现在 Sharding-JDBC实现分库分表。主要业务场景是对日志表进行安装ID hash 进行拆分,避免生产大数据量存储问题。在Java的JDBC层以对业务应用零侵入的方式额外提供数据分片,读写分离,柔性事务和分布式治理能力。

8. 前端解决方案,数据驱动视图

  • 只需要的配置每列属性即可完成入上图 令牌管理CRUD,是不是很神奇? 下边对两种写法进行对比,读者可以从代码量中比较出差异,实现的效果完全一致,让规则数据可以直接可视化。

 
   
   
 
  1. // pig 前端写法

  2. export const tableOption = {

  3. border: true,

  4. index: true,

  5. indexLabel: '序号',

  6. stripe: true,

  7. menuAlign: 'center',

  8. align: 'center',

  9. viewBtn: true,

  10. addBtn: false,

  11. editBtn: false,

  12. delBtn: false,

  13. column: [{

  14. label: '用户ID',

  15. prop: 'user_id',

  16. align: 'center'

  17. }

  18. ...

  19. ]

  20. }


  21. // element 原生写法

  22. <template>

  23. <el-table

  24. :data="tableData"

  25. style="width: 100%">

  26. <el-table-column

  27. prop="date"

  28. label="日期"

  29. width="180">

  30. </el-table-column>

  31. ...

  32. </el-table>

  33. </template>


  34. <script>

  35. export default {

  36. data() {

  37. return {

  38. tableData: [{

  39. date: '2016-05-02',

  40. name: '王小虎',

  41. address: '上海市普陀区金沙江路 1518 弄'

  42. }

  43. ...

  44. ]

  45. }

  46. }

  47. }

  48. </script>


项目截图

干货 | 微服务架构下 Spring Cloud OAuth2 通用权限管理系统干货 | 微服务架构下 Spring Cloud OAuth2 通用权限管理系统

9. 总结

  1. Pig 1.0 已经在多家公司内部生产运行,各方面表现还不错。提供了从开发到生产的整套解决方案

  2. Pig 2.0 聚焦瘦身减少中间件的引入,目的是让更多人快速以pig入门Spring Cloud 提供完整的组件链,快速上手。

  3. 限于篇幅以上只是对部分功能进行说明,欢迎大家交流学习

以下专题教程也许您会有兴趣

  • 《Spring Boot 2.x 系列教程》 https://www.bysocket.com/springboot

  • 《Java 核心系列教程》 https://www.bysocket.com/archives/2100


由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!

-The End-

号外:为读者持续几份最新教程,覆盖了 Spring Boot、Spring Cloud、微服务架构等。



 热门文章:




推荐号主

干货 | 微服务架构下 Spring Cloud OAuth2 通用权限管理系统


文末力荐公益

聆听做技术你的困惑,并解答


如果文章对你有帮助的话

↓↓↓↓

以上是关于微服务+vue架构+oauth2+权限系统的实现的主要内容,如果未能解决你的问题,请参考以下文章

基于SpringCloud的微服务架构权限管理系统

使用微服务架构思想,设计部署OAuth2.0授权认证框架

权限设计系列「认证授权专题」微服务架构的登陆认证问题

OAuth2.0 原理流程及其单点登录和权限控制

OAuth2.0 原理流程及其单点登录和权限控制

#私藏项目实操分享#权限设计系列「认证授权专题」微服务架构的登陆认证问题