当前位置: 代码网 > it编程>编程语言>Java > Gateway+Springsecurity+OAuth2.0+JWT 实现分布式统一认证授权!

Gateway+Springsecurity+OAuth2.0+JWT 实现分布式统一认证授权!

2024年08月02日 Java 我要评论
Gateway OAuth2.0

目录

1. oauth2.0授权服务

2. 资源服务

3. gateway网关

4. 测试


 

springsecurity+oauth2.0 搭建认证中心和资源服务中心-csdn博客 ​​​​​​

基础上整合网关和jwt实现分布式统一认证授权。

 

大致流程如下:

1、客户端发出请求给网关获取令牌

2、网关收到请求,直接转发给授权服务

3、授权服务验证用户名、密码等一系列身份,通过则颁发令牌给客户端

4、客户端携带令牌请求资源,请求直接到了网关层

5、网关对令牌进行校验(验签、过期时间校验....)、鉴权(对当前令牌携带的权限)和访问资源所需的权限进行比对,如果权限有交集则通过校验,直接转发给微服务

6、微服务进行逻辑处理

1. oauth2.0授权服务

导入依赖

<dependencies>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-security</artifactid>
            <version>2.2.5.release</version>
        </dependency>
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-oauth2</artifactid>
            <version>2.2.5.release</version>
        </dependency>
        <dependency>
            <groupid>mysql</groupid>
            <artifactid>mysql-connector-java</artifactid>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupid>com.baomidou</groupid>
            <artifactid>mybatis-plus-boot-starter</artifactid>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>spring-security-oauth2-resource-server</artifactid>
        </dependency>
        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <version>1.18.30</version>
        </dependency>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
        </dependency>
        <dependency>
            <groupid>com.alibaba.cloud</groupid>
            <artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
        </dependency>
    </dependencies>

application.yaml

server:
  port: 8080
spring:
  application:
    name: oauth2-cloud-auth-server
  cloud:
    nacos:
      ## 注册中心配置
      discovery:
        # nacos的服务地址,nacos-server中ip地址:端口号
        server-addr: 127.0.0.1:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.driver
    url: jdbc:mysql://localhost:3306/rbac?useunicode=true&characterencoding=utf-8&servertimezone=utc
    username: root
    password: 123456

 这里展示部分代码

accesstokenconfig类

/**
 * 令牌的配置
 */
@configuration
public class accesstokenconfig {
    /**
     * jwt的秘钥
     * todo 实际项目中需要统一配置到配置文件中,资源服务也需要用到
     */
    private final static string sign_key="jwt";

    /**
     * 令牌的存储策略
     */
    @bean
    public tokenstore tokenstore() {
        //使用jwttokenstore生成jwt令牌
        return new jwttokenstore(jwtaccesstokenconverter());
    }

    /**
     * jwtaccesstokenconverter
     * tokenenhancer的子类,在jwt编码的令牌值和oauth身份验证信息之间进行转换。
     * todo:后期可以使用非对称加密
     */
    @bean
    public jwtaccesstokenconverter jwtaccesstokenconverter(){
        jwtaccesstokenconverter converter = new jwtaccesstokenconverter();
        // 设置秘钥
        converter.setsigningkey(sign_key);
        return converter;
    }

    @bean
    passwordencoder passwordencoder() {
        return new bcryptpasswordencoder();
    }
}

myauthorizationconfig类

@configuration
@enableauthorizationserver
public class myauthorizationconfig extends authorizationserverconfigureradapter {

   @autowired
   private tokenstore tokenstore;
    /**
     * 客户端存储策略,这里使用内存方式,后续可以存储在数据库
     */
    @autowired
    private clientdetailsservice clientdetailsservice;

    /**
     * security的认证管理器,密码模式需要用到
     */
    @autowired
    private authenticationmanager authenticationmanager;

    @autowired
    private jwtaccesstokenconverter jwtaccesstokenconverter;

    /**
     * 配置令牌访问的安全约束
     */
    @override
    public void configure(authorizationserversecurityconfigurer security) throws exception {
        security
                //开启/oauth/token_key验证端口权限访问
                .tokenkeyaccess("permitall()")
                //开启/oauth/check_token验证端口认证权限访问
                .checktokenaccess("permitall()")
                //表示支持 client_id 和 client_secret 做登录认证
                .allowformauthenticationforclients();
    }
    //配置客户端
    @override
    public void configure(clientdetailsserviceconfigurer clients) throws exception {
        //内存模式
        clients.inmemory()
                //客户端id
                .withclient("test")
                //客户端秘钥
                .secret(new bcryptpasswordencoder().encode("123456"))
                //资源id,唯一,比如订单服务作为一个资源,可以设置多个
                .resourceids("order")
                //授权模式,总共四种,1. authorization_code(授权码模式)、password(密码模式)、client_credentials(客户端模式)、implicit(简化模式)
                //refresh_token并不是授权模式,
                .authorizedgranttypes("authorization_code","password","client_credentials","implicit","refresh_token")
                //允许的授权范围,客户端的权限,这里的all只是一种标识,可以自定义,为了后续的资源服务进行权限控制
                .scopes("all")
                //false 则跳转到授权页面
                .autoapprove(false)
                //授权码模式的回调地址
                .redirecturis("http://www.baidu.com"); //可以and继续添加客户端
    }


    @bean
    public authorizationservertokenservices tokenservices() {
        defaulttokenservices services = new defaulttokenservices();
        //客户端端配置策略
        services.setclientdetailsservice(clientdetailsservice);
        //支持令牌的刷新
        services.setsupportrefreshtoken(true);
        //令牌服务
        services.settokenstore(tokenstore);
        //access_token的过期时间
        services.setaccesstokenvalidityseconds(60 * 60 * 2);
        //refresh_token的过期时间
        services.setrefreshtokenvalidityseconds(60 * 60 * 24 * 3);

        //设置令牌增强,使用jwt
        services.settokenenhancer(jwtaccesstokenconverter);
        return services;
    }
    /**
     * 授权码模式的service,使用授权码模式authorization_code必须注入
     */
    @bean
    public authorizationcodeservices authorizationcodeservices() {
        //授权码存在内存中
        return new inmemoryauthorizationcodeservices();
    }

    /**
     * 配置令牌访问的端点
     */
    @override
    public void configure(authorizationserverendpointsconfigurer endpoints) throws exception {
        endpoints
                //授权码模式所需要的authorizationcodeservices
                .authorizationcodeservices(authorizationcodeservices())
                //密码模式所需要的authenticationmanager
                .authenticationmanager(authenticationmanager)
                //令牌管理服务,无论哪种模式都需要
                .tokenservices(tokenservices())
                //只允许post提交访问令牌,uri:/oauth/token
                .allowedtokenendpointrequestmethods(httpmethod.post);
    }
}

securityconfig类

/**
 * spring security的安全配置
 */
@configuration
@enablewebsecurity
public class securityconfig extends websecurityconfigureradapter {

    /**
     * 加密算法
     */

    @autowired
    jwttokenuserdetailsservice userdetailsservice;

    @override
    protected void configure(httpsecurity http) throws exception {
        //todo 允许表单登录
        http.authorizerequests()
                .anyrequest().authenticated()
                .and()
                .formlogin()
                .loginprocessingurl("/login")
                .permitall()
                .and()
                .csrf()
                .disable();
    }

    @override
    protected void configure(authenticationmanagerbuilder auth) throws exception {
       //从数据库中查询用户信息
        auth.userdetailsservice(userdetailsservice);
    }



    /**
     * authenticationmanager对象在oauth2认证服务中要使用,提前放入ioc容器中
     * oauth的密码模式需要
     */
    @override
    @bean
    public authenticationmanager authenticationmanagerbean() throws exception {
        return super.authenticationmanagerbean();
    }
}

2. 资源服务

导入依赖

  <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>
        <dependency>
        <groupid>cn.hutool</groupid>
        <artifactid>hutool-all</artifactid>
       </dependency>
        <dependency>
            <groupid>com.alibaba</groupid>
            <artifactid>fastjson</artifactid>
            <version>1.2.78</version>
        </dependency>
        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <version>1.18.30</version>
        </dependency>
        <dependency>
            <groupid>com.alibaba.cloud</groupid>
            <artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
        </dependency>

application.yaml

server:
  port: 8081
spring:
  application:
    name: oauth2-cloud-service
  cloud:
    nacos:
      ## 注册中心配置
      discovery:
        # nacos的服务地址,nacos-server中ip地址:端口号
        server-addr: 127.0.0.1:8848

 authenticationfilter类

@component
public class authenticationfilter extends onceperrequestfilter {
    /**
     * 具体方法主要分为两步
     * 1. 解密网关传递的信息
     * 2. 将解密之后的信息封装放入到request中
     */
    @override
    protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain) throws servletexception, ioexception {
        //获取请求头中的用户信息
        string token = request.getheader("token");
        if (token!=null){
            //解密
            string json = base64.decodestr(token);
            jsonobject jsonobject = json.parseobject(json);
            //获取用户身份信息、权限信息
            string principal = jsonobject.getstring("user_name");
            jsonarray tempjsonarray = jsonobject.getjsonarray("authorities");
            //权限
            string[] authorities =  tempjsonarray.toarray(new string[0]);
            //放入loginval
            loginval loginval = new loginval();
          
            loginval.setusername(principal);
            loginval.setauthorities(authorities);
            
            //放入request的attribute中
            request.setattribute("login_message",loginval);
        }
        filterchain.dofilter(request,response);
    }
}

servicecontroller类

@restcontroller
public class servicecontroller {

    @requestmapping("/test")
    public loginval test(httpservletrequest httpservletrequest){
       return  (loginval)httpservletrequest.getattribute("login_message");
    }
}

3. gateway网关

导入依赖

 <dependencies>
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-gateway</artifactid>
        </dependency>
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-security</artifactid>
            <version>2.2.5.release</version>
        </dependency>
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-oauth2</artifactid>
            <version>2.2.5.release</version>
        </dependency>
        <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>spring-security-oauth2-resource-server</artifactid>
        </dependency>
        <dependency>
            <groupid>com.alibaba.cloud</groupid>
            <artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
        </dependency>
        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
        </dependency>
        <dependency>
            <groupid>cn.hutool</groupid>
            <artifactid>hutool-all</artifactid>
        </dependency>
        <dependency>
            <groupid>com.alibaba</groupid>
            <artifactid>fastjson</artifactid>
            <version>1.2.78</version>
        </dependency>
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-loadbalancer</artifactid>
        </dependency>
    </dependencies>

application.yaml

server:
  port: 7000
spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: oauth2-cloud-gateway
  cloud:
    nacos:
      ## 注册中心配置
      discovery:
        # nacos的服务地址,nacos-server中ip地址:端口号
        server-addr: 127.0.0.1:8848
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: oauth2-cloud-auth-server
          uri: lb://oauth2-cloud-auth-server
          predicates:
            ## path route predicate factory断言
            - path=/oauth/**
        - id: oauth2-cloud-order
          uri: lb://oauth2-cloud-service
          predicates:
            ## path route predicate factory断言
            - path=/test/**

oauth2:
  cloud:
    sys:
      parameter:
        ignoreurls:
          - /oauth/token
          - /oauth/authorize

 accesstokenconfig类

/**
 * 令牌的配置
 */
@configuration
public class accesstokenconfig {
    private final static string sign_key="jwt";

    /**
     * 令牌的存储策略
     */
    @bean
    public tokenstore tokenstore() {
        //使用jwttokenstore生成jwt令牌
        return new jwttokenstore(jwtaccesstokenconverter());
    }

    /**
     * jwtaccesstokenconverter
     * tokenenhancer的子类,在jwt编码的令牌值和oauth身份验证信息之间进行转换。
     * todo:后期可以使用非对称加密
     */
    @bean
    public jwtaccesstokenconverter jwtaccesstokenconverter(){
        jwtaccesstokenconverter converter = new jwtaccesstokenconverter();
        // 设置秘钥
        converter.setsigningkey(sign_key);
        return converter;
    }
}

jwtaccessmanager类

@slf4j
@component
//经过认证管理器jwtauthenticationmanager认证成功后,就需要对令牌进行鉴权,如果该令牌无访问资源的权限,则不允通过。
public class jwtaccessmanager implements reactiveauthorizationmanager<authorizationcontext> {



    @override
    public mono<authorizationdecision> check(mono<authentication> mono, authorizationcontext authorizationcontext) {

        uri uri = authorizationcontext.getexchange().getrequest().geturi();
        //设计权限角色,这里简单写一下,实际上应该从数据库或者缓存中获取
        list<string> authorities = new arraylist<>();
        authorities.add("role_admin");
        //认证通过且角色匹配的用户可访问当前路径
        return mono
                //判断是否认证成功
                .filter(authentication::isauthenticated)
                //获取认证后的全部权限
                .flatmapiterable(authentication::getauthorities)
                .map(grantedauthority::getauthority)
                //如果权限包含则判断为true
                .any(authorities::contains)
                .map(authorizationdecision::new)
                .defaultifempty(new authorizationdecision(false));
    }

}

jwtauthenticationmanager类

/**
 * jwt认证管理器,主要的作用就是对携带过来的token进行校验,比如过期时间,加密方式等
 * 一旦token校验通过,则交给鉴权管理器进行鉴权
 */
@component
public class jwtauthenticationmanager implements reactiveauthenticationmanager {
    /**
     * 使用jwt令牌进行解析令牌
     */
    @autowired
    private tokenstore tokenstore;

    @override
    public mono<authentication> authenticate(authentication authentication) {
        return mono.justorempty(authentication)
                .filter(a -> a instanceof bearertokenauthenticationtoken)
                .cast(bearertokenauthenticationtoken.class)
                .map(bearertokenauthenticationtoken::gettoken)
                .flatmap((accesstoken -> {
                    oauth2accesstoken oauth2accesstoken = this.tokenstore.readaccesstoken(accesstoken);
                    //根据access_token从数据库获取不到oauth2accesstoken
                    if (oauth2accesstoken == null) {
                        return mono.error(new invalidtokenexception("无效的token!"));
                    } else if (oauth2accesstoken.isexpired()) {
                        return mono.error(new invalidtokenexception("token已过期!"));
                    }
                    oauth2authentication oauth2authentication = this.tokenstore.readauthentication(accesstoken);
                    if (oauth2authentication == null) {
                        return mono.error(new invalidtokenexception("无效的token!"));
                    } else {
                        return mono.just(oauth2authentication);
                    }
                })).cast(authentication.class);
    }
}

securityconfig类

@configuration
@enablewebfluxsecurity
public class securityconfig {

    /**
     * jwt的鉴权管理器
     */
    @autowired
    private reactiveauthorizationmanager<authorizationcontext> accessmanager;

    @autowired
    private requestauthenticationentrypoint requestauthenticationentrypoint;

    @autowired
    private requestaccessdeniedhandler requestaccessdeniedhandler;

    /**
     * 系统参数配置
     */
    @autowired
    private sysparameterconfig sysconfig;

    /**
     * token校验管理器
     */
    @autowired
    private reactiveauthenticationmanager tokenauthenticationmanager;

    @autowired
    private corsfilter corsfilter;

    @bean
    securitywebfilterchain webfluxsecurityfilterchain(serverhttpsecurity http) throws exception{
        //认证过滤器,放入认证管理器tokenauthenticationmanager
        authenticationwebfilter authenticationwebfilter = new authenticationwebfilter(tokenauthenticationmanager);
        authenticationwebfilter.setserverauthenticationconverter(new serverbearertokenauthenticationconverter());

        http
                .httpbasic().disable()
                .csrf().disable()
                .authorizeexchange()
                //白名单直接放行
                .pathmatchers(arrayutil.toarray(sysconfig.getignoreurls(),string.class)).permitall()
                //其他的请求必须鉴权,使用鉴权管理器
                .anyexchange().access(accessmanager)
                //异常处理
                .and().exceptionhandling()
                .authenticationentrypoint(requestauthenticationentrypoint)
                .accessdeniedhandler(requestaccessdeniedhandler)
                .and()
                // 跨域过滤器
                .addfilterat(corsfilter, securitywebfiltersorder.cors)
                //token的认证过滤器,用于校验token和认证
                .addfilterat(authenticationwebfilter, securitywebfiltersorder.authentication);
        return http.build();
    }
}

requestaccessdeniedhandler

/**

 * 自定义返回结果:没有权限访问时
 */
@component
public class requestaccessdeniedhandler implements serveraccessdeniedhandler {
    @override
    public mono<void> handle(serverwebexchange exchange, accessdeniedexception denied) {
        serverhttpresponse response = exchange.getresponse();
        response.setstatuscode(httpstatus.ok);
        response.getheaders().add(httpheaders.content_type, mediatype.application_json_value);
        string body= jsonutil.tojsonstr(new resultmsg(1005,"无权限访问",null));
        databuffer buffer =  response.bufferfactory().wrap(body.getbytes(charset.forname("utf-8")));
        return response.writewith(mono.just(buffer));
    }
}

globalauthenticationfilter

/**
 * 全局过滤器,对token的拦截,解析token放入header中,便于下游微服务获取用户信息
 * 分为如下几步:
 *  1、白名单直接放行
 *  2、校验token
 *  3、读取token中存放的用户信息
 *  4、重新封装用户信息,加密成功json数据放入请求头中传递给下游微服务
 */
@component
@slf4j
public class globalauthenticationfilter implements globalfilter, ordered {
    /**
     * jwt令牌的服务
     */
    @autowired
    private tokenstore tokenstore;



    /**
     * 系统参数配置
     */
    @autowired
    private sysparameterconfig sysconfig;


    @override
    public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
        string requesturl = exchange.getrequest().getpath().value();
        //1、白名单放行,比如授权服务、静态资源.....
        if (checkurls(sysconfig.getignoreurls(),requesturl)){
            return chain.filter(exchange);
        }

        //2、 检查token是否存在
        string token = gettoken(exchange);
        if (stringutils.isblank(token)) {
            return invalidtokenmono(exchange);
        }

        //3 判断是否是有效的token
        oauth2accesstoken oauth2accesstoken;
        try {
            //解析token,使用tokenstore
            oauth2accesstoken = tokenstore.readaccesstoken(token);
            map<string, object> additionalinformation = oauth2accesstoken.getadditionalinformation();
            system.out.println(additionalinformation);

            //取出用户身份信息
            string user_name = additionalinformation.get("user_name").tostring();
            //获取用户权限
            list<string> authorities = (list<string>) additionalinformation.get("authorities");

            //将用户名和权限进行base64加密
            jsonobject jsonobject=new jsonobject();
            jsonobject.put("user_name", user_name);
            jsonobject.put("authorities",authorities);
            string base = base64.encode(jsonobject.tojsonstring());


           // serverhttprequest 中的 mutate 方法是用于创建一个修改后的请求对象的方法,而不改变原始请求对象。这个方法是为了在处理请求过程中创建一个新的请求对象,以便进行一些修改或增强。
            serverhttprequest tokenrequest = exchange.getrequest().mutate().header("token",base).build();
            serverwebexchange build = exchange.mutate().request(tokenrequest).build();
            return chain.filter(build);
        } catch (invalidtokenexception e) {
            //解析token异常,直接返回token无效
            return invalidtokenmono(exchange);
        }


    }

    @override
    public int getorder() {
        return 0;
    }

    /**
     * 对url进行校验匹配
     */
    private boolean checkurls(list<string> urls,string path){
        antpathmatcher pathmatcher = new antpathmatcher();
        for (string url : urls) {
            if (pathmatcher.match(url,path))
                return true;
        }
        return false;
    }

    /**
     * 从请求头中获取token
     */
    private string gettoken(serverwebexchange exchange) {
        string tokenstr = exchange.getrequest().getheaders().getfirst("authorization");
        if (stringutils.isblank(tokenstr)) {
            return null;
        }
        string token = tokenstr.split(" ")[1];
        if (stringutils.isblank(token)) {
            return null;
        }
        return token;
    }

    /**
     * 无效的token
     */
    private mono<void> invalidtokenmono(serverwebexchange exchange) {
        return buildreturnmono(resultmsg.builder()
                .code(1004)
                .msg("无效的token")
                .build(), exchange);
    }


    private mono<void> buildreturnmono(resultmsg resultmsg, serverwebexchange exchange) {
        serverhttpresponse response = exchange.getresponse();
        byte[] bits = json.tojsonstring(resultmsg).getbytes(standardcharsets.utf_8);
        databuffer buffer = response.bufferfactory().wrap(bits);
        response.setstatuscode(httpstatus.unauthorized);
        response.getheaders().add("content-type", "application/json;charset:utf-8");
        return response.writewith(mono.just(buffer));
    }
}

sysparameterconfig

@configurationproperties(prefix = "oauth2.cloud.sys.parameter")
@data
@component
public class sysparameterconfig {
    /**
     * 白名单
     */
    private list<string> ignoreurls;
}

4. 测试

 

代码链接:gateway+springsecurity+oauth2.0+jwt实现分布式统一认证授权资源-csdn文库 

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com