常用中间件-OAuth2

1. 微服务权限校验Session共享

1.1 微服务权限校验

常用中间件-OAuth2_第1张图片
常用中间件-OAuth2_第2张图片
实现2号方案。使用Redis作为Session统一存储。

首先配置一下Nacos,参考https://blog.csdn.net/weixin_43917045/article/details/132852850

然后为每个服务添加验证机制,先导入依赖
常用中间件-OAuth2_第3张图片

        <!--  SpringSession Redis支持  -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <!--  添加Redis的Starter  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
		<!--使用SpringSecurity框架作为权限校验框架:-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

三个服务都需要添加以上依赖。

然后给三个服务都修改配置文件。
常用中间件-OAuth2_第4张图片
这样,默认情况下每个服务的接口都会被SpringSecurity所保护,只有登录成功后,才可以被访问。
启动nacos和三个服务。
此时访问borrow借阅接口http://localhost:8201/borrow/1会自动跳转到登陆接口http://localhost:8201/login
常用中间件-OAuth2_第5张图片
常用中间件-OAuth2_第6张图片
常用中间件-OAuth2_第7张图片
点击登录后进入redis-cli可以看到用户密码已存在。
常用中间件-OAuth2_第8张图片
此时访问book服务,可以看到直接能调用,不用登录,因为上次登录在borrow服务访问中已经存入账户密码信息。
常用中间件-OAuth2_第9张图片
此时先退出登录。
常用中间件-OAuth2_第10张图片
退出账户后访问http://localhost:8301/book/1就会跳转到登录页面http://localhost:8301/book/1
然后再进行登录,进行借阅接口访问,发现出错,是由于borrow服务调用user和book服务是远程调用形式,,而这种形式没有进行任何验证。出现这种情况原因是RestTemplate远程调用的时候,由于请求没有携带Session的Cookies,所以导致验证失败,访问不成功,返回401。因此存在不便。
常用中间件-OAuth2_第11张图片

2. OAuth 2.0实现单点登录

常用中间件-OAuth2_第12张图片
常用中间件-OAuth2_第13张图片
常用中间件-OAuth2_第14张图片
常用中间件-OAuth2_第15张图片
常用中间件-OAuth2_第16张图片

2.1 搭建验证服务器

还原不带任何spring cloud alibaba依赖的项目,
常用中间件-OAuth2_第17张图片
加入spring cloud依赖
常用中间件-OAuth2_第18张图片
创建auth-service(左上角新增moudle)并导入依赖。(图片中服务名称a打成o了)
常用中间件-OAuth2_第19张图片
常用中间件-OAuth2_第20张图片
编写配置类
常用中间件-OAuth2_第21张图片

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()  //
                .and()
                .formLogin().permitAll();    //使用表单登录
    }
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        auth
                .inMemoryAuthentication()   //直接创建一个用户,懒得搞数据库了
                .passwordEncoder(encoder)
                .withUser("test").password(encoder.encode("123456")).roles("USER");
    }
  
  	@Bean   //这里需要将AuthenticationManager注册为Bean,在OAuth配置中使用
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
@EnableAuthorizationServer   //开启验证服务器
@Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

    @Resource
    private AuthenticationManager manager;

    private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    /**
     * 这个方法是对客户端进行配置,一个验证服务器可以预设很多个客户端,
     * 之后这些指定的客户端就可以按照下面指定的方式进行验证
     * @param clients 客户端配置工具
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()   //这里我们直接硬编码创建,当然也可以像Security那样自定义或是使用JDBC从数据库读取
                .withClient("web")   //客户端名称,随便起就行
                .secret(encoder.encode("654321"))      //只与客户端分享的secret,随便写,但是注意要加密
                .autoApprove(false)    //自动审批,这里关闭,要的就是一会体验那种感觉
                .scopes("book", "user", "borrow")     //授权范围,这里我们使用全部all
                .authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");
                //授权模式,一共支持5种,除了之前我们介绍的四种之外,还有一个刷新Token的模式
                //这里我们直接把五种都写上,方便一会实验,当然各位也可以单独只写一种一个一个进行测试
                //现在我们指定的客户端就支持这五种类型的授权方式了
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .passwordEncoder(encoder)    //编码器设定为BCryptPasswordEncoder
                .allowFormAuthenticationForClients()  //允许客户端使用表单验证,一会我们POST请求中会携带表单信息
                .checkTokenAccess("permitAll()");     //允许所有的Token查询请求
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(manager);
        //由于SpringSecurity新版本的一些底层改动,这里需要配置一下authenticationManager,才能正常使用password模式
    }
}

然后启动auth-service。
常用中间件-OAuth2_第22张图片
使用postman进行接口测试。

2.1.1客户端模式测试,

客户端模式只需要提供id和secret即可直接拿到token,但还需要添加一个grant_type表面我们的授权方式,默认请求路径为http://localhost:8500/sso/oauth/token
常用中间件-OAuth2_第23张图片
通过访问http://localhost:8500/sso/oauth/check_token来验证token是否有效
常用中间件-OAuth2_第24张图片

2.1.2 密码模式测试

密码模式还需要提供具体的用户名和密码,授权模式定义为password即可。
常用中间件-OAuth2_第25张图片
还需要在请求头中添加Basic验证信息,直接写id和secret即可
常用中间件-OAuth2_第26张图片
常用中间件-OAuth2_第27张图片
常用中间件-OAuth2_第28张图片
常用中间件-OAuth2_第29张图片

2.1.3 隐式授权模式

这种模式需要在验证服务器上进行登录操作,而不是直接请求Token,验证登陆地址http://localhost:8500/sso/oauth/authorize?client_id=web&response_type=token
response_type一定是token类型,这样才会返回Token。
请求上边的验证登陆地址则会进入登录页面。

常用中间件-OAuth2_第30张图片
登录之后出现错误,这是因为登陆后验证服务器需要将结果返回给客户端,所以需要提供客户端的回调地址,这样浏览器会被重定向到指定的回调地址并且请求中回携带Token信息,此时随便配置一个回调地址,
常用中间件-OAuth2_第31张图片
配置回调地址,然后重启
常用中间件-OAuth2_第32张图片
此时会要求进行授权,
常用中间件-OAuth2_第33张图片
常用中间件-OAuth2_第34张图片
常用中间件-OAuth2_第35张图片

2.1.4 授权码模式-最安全

这种方式和1.2.3的流程一样,但是请求的地址是code类型:http://localhost:8500/sso/oauth/authorize?client_id=web&response_type=code,访问该地址,因为在1.2.3已经登录过了,可以看到访问之后,依然会进入到回调地址,但是此时给的是授权码code了,不是直接给token。但是有时候可能需要token。
常用中间件-OAuth2_第36张图片

按照2.1之前的第四个授权码图示原来,需要携带授权码secret一起请求,才能拿到token,正常情况下是由回调的服务器进行处理,在postman中测试进行,复制刚得到的授权码,接口请求localhost:8500/sso/oauth/token
可以看到正常拿到token
常用中间件-OAuth2_第37张图片
以上四种基本的Token请求方式。

2.1.5 刷新令牌

当Token过期时,就可以使用这个refresh_token来申请一个新的Token。
常用中间件-OAuth2_第38张图片
重新请求
常用中间件-OAuth2_第39张图片
改为点击发送,出现异常
常用中间件-OAuth2_第40张图片
在这里插入图片描述
查看日志发现,还需要单独配置一个UserDetailsService,我们直接把Security中的实例注册为Bean。
常用中间件-OAuth2_第41张图片
然后再Endpoint中设置
常用中间件-OAuth2_第42张图片
常用中间件-OAuth2_第43张图片

添加完重启,重启之后token就没有了,需要重新申请以下。
访问http://localhost:8500/sso/oauth/authorize?client_id=web&response_type=code,先进行登录test,123456,然后授权获得code
常用中间件-OAuth2_第44张图片

常用中间件-OAuth2_第45张图片
获得code
常用中间件-OAuth2_第46张图片
获得token
常用中间件-OAuth2_第47张图片
访问接口刷新token
常用中间件-OAuth2_第48张图片

3. 单点登录客户端

前面已经将服验证服务器搭建完成了,下面实现单点登录,SpringCloud提供了客户端的直接实现,只需要添加一个注解和少量配置即可将我们的服务作为一个单点登录应用,使用的是第四张授权码模式。
这种模式只是将验证方式由原来的默认登陆形式改变为了统一在授权服务器登录的形式。

3.1 单点登录(基于@EnableOAuth2Sso实现)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

在book-service中添加依赖
常用中间件-OAuth2_第49张图片
启动类添加注解
常用中间件-OAuth2_第50张图片常用中间件-OAuth2_第51张图片
常用中间件-OAuth2_第52张图片
然后需要在配置文件中配置验证服务器相关的信息:

security:
  oauth2:
    client:
      #不多说了
      client-id: web
      client-secret: 654321
      #Token获取地址
      access-token-uri: http://localhost:8500/sso/oauth/token
      #验证页面地址
      user-authorization-uri: http://localhost:8500/sso/oauth/authorize
    resource:
      #Token信息获取和校验地址
      token-info-uri: http://localhost:8500/sso/oauth/check_token

常用中间件-OAuth2_第53张图片
启动book服务
常用中间件-OAuth2_第54张图片
访问,由于book服务是8301端口,需要将重定向地址也改为8301
常用中间件-OAuth2_第55张图片在这里插入图片描述

然后会登录跳转到授权页面
常用中间件-OAuth2_第56张图片
常用中间件-OAuth2_第57张图片
授权后访问成功
常用中间件-OAuth2_第58张图片
此时访问成功了但,但是用户信息是否也一并保存过来了,直接获取以下SpringSecurity的Context查看用户信息,获取方式和之前一样
常用中间件-OAuth2_第59张图片
再次访问book服务,输出用户信息
在这里插入图片描述
在这里插入图片描述
接着将所有的服务都使用这种方式进行验证,将重定向地址给所有服务都加上
常用中间件-OAuth2_第60张图片
将borrow-service,user-service都加上依赖,配置文件内容,和启动类的注释。
常用中间件-OAuth2_第61张图片
常用中间件-OAuth2_第62张图片
常用中间件-OAuth2_第63张图片
配置完启动user和borrow服务和auth服务
常用中间件-OAuth2_第64张图片
此时访问user服务和book服务都是授权后可登录。但是此时三个服务session会互相占用,若访问完user服务,再访问user服务不会再次验证,但是若再访问book或其他服务就会需要再次验证。

这里有两个方案:一是和之前一样Session统一存储。二是设置context-path路径,每个服务单独设置,就不会互相占用了。

常用中间件-OAuth2_第65张图片
常用中间件-OAuth2_第66张图片
单点登录无法解决服务间调用的问题。

3.2 单点登录(基于@EnableResourceServer实现)

前面已经实现了将服务作为单点登录应用直接实现单点登录。但是如果是第三方访问,则我们就需要将服务作为资源服务了,作为资源服务就不会再提供验证的过长,而是直接要求请求时携带Token,而验证过程使用postman进行测试。
总之,与上边实现的相比,下边实现的访问过程只需要携带Token就能访问这些资源服务器了,客户端被独立了出来,用于携带Token去访问这些服务。

此时需要添加注解和少量配置
常用中间件-OAuth2_第67张图片
常用中间件-OAuth2_第68张图片
常用中间件-OAuth2_第69张图片
修改完配置和注解重启book-service,访问book服务,显示没有权限访问资源。

这是由于请求头中没有携带token信息,有两种方式可访问到资源
一:在URL后添加 access_token 请求参数,值为Token值
二:在请求头中添加 Authorization 值为 Bearer + Token值

常用中间件-OAuth2_第70张图片
在URL后添加 access_token 请求参数 此时通过密码模式访问拿到token
常用中间件-OAuth2_第71张图片

后缀参数加上获取的access_token即可访问
常用中间件-OAuth2_第72张图片在请求头中添加 Authorization 值为 Bearer + Token值
直接访问没有权限
常用中间件-OAuth2_第73张图片
常用中间件-OAuth2_第74张图片
常用中间件-OAuth2_第75张图片
常用中间件-OAuth2_第76张图片
然后可访问资源
常用中间件-OAuth2_第77张图片
到此,资源服务器就搭建完成

3.3 资源服务器深度自定义

编写一个配置类,使得用户授权了某个Scope才可以访问此服务。例如,必须有book作用域才能访问book-service服务
常用中间件-OAuth2_第78张图片

@Configuration
public class ResourceConfiguration extends ResourceServerConfigurerAdapter { //继承此类进行高度自定义

    @Override
    public void configure(HttpSecurity http) throws Exception {  //这里也有HttpSecurity对象,方便我们配置SpringSecurity
        http
                .authorizeRequests()
                .anyRequest().access("#oauth2.hasScope('lbwnb')");  //添加自定义规则
        //Token必须要有我们自定义scope授权才可以访问此资源
    }
}

添加完重启book-service
常用中间件-OAuth2_第79张图片
访问发现作用域不足
常用中间件-OAuth2_第80张图片
在这里插入图片描述重启后访问正常
常用中间件-OAuth2_第81张图片

实际上资源服务器完全没有必要将Security的信息保存在Session中,因为现在只需要将Token告诉资源服务器,那么资源服务器就可以联系验证服务器得到用户信息,不需要使用之前的Session存储机制了,所以会发现HttpSession中没有SPRING_SECURITY_CONTEXT,现在Security信息都是通过连接资源服务器获取。

常用中间件-OAuth2_第82张图片
但是目前每次访问都需要去验证一次,浪费资源,这种可以通过JTW去解决。
常用中间件-OAuth2_第83张图片
接着将所有服务都改为基于@EnableResourceServer方式实现,borrow-service和user-service的配置文件,启动类的注解都要修改

常用中间件-OAuth2_第84张图片
常用中间件-OAuth2_第85张图片
然后启动user、book、borrow服务,此时这三个服务都是资源服务。

访问user服务
常用中间件-OAuth2_第86张图片

访问borrow服务还是会出问题,因为远程调用也需要携带token,但是远程调用过程没有携带任何token’信息。因此需要想办法将用户传来的token信息在进行远程调用同时也携带上。因此可以直接使用OAuth2RestTemplate,它会在请求其他服务时携带当前请求的Token信息,它继承自RestTemplate,直接定义一个Bean

@Configuration
public class WebConfiguration {

    @Resource
    OAuth2ClientContext context;

    @Bean
    public OAuth2RestTemplate restTemplate(){
        return new OAuth2RestTemplate(new ClientCredentialsResourceDetails(), context);
    }
}

常用中间件-OAuth2_第87张图片常用中间件-OAuth2_第88张图片
常用中间件-OAuth2_第89张图片
修改后重启borrow,然后访问borrow服务,此时正常调用。这样远程调用的时候就会也携带token信息
常用中间件-OAuth2_第90张图片

3.4 Nacos加入,通过Feign实现远程调用

加入依赖

## 给最外层pom添加
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2021.0.1.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
## 给每个服务添加
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

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

常用中间件-OAuth2_第91张图片
常用中间件-OAuth2_第92张图片配置Nacos服务
常用中间件-OAuth2_第93张图片
配置完启动nacos
常用中间件-OAuth2_第94张图片
常用中间件-OAuth2_第95张图片
三个服务分别添加配置
常用中间件-OAuth2_第96张图片

常用中间件-OAuth2_第97张图片
常用中间件-OAuth2_第98张图片
修改borrow服务service中的调用方式
常用中间件-OAuth2_第99张图片
配置完启动三个服务,进入nacos查看
常用中间件-OAuth2_第100张图片
访问borrow服务,此时已经加入了nacos、负载均衡。其中主要是要加入依赖和在WebConfiguration方法上加入@LoadBalanced注解
常用中间件-OAuth2_第101张图片

3.4.1 加入openfeign

在borrow服务添加依赖
常用中间件-OAuth2_第102张图片在borrow服务下写客户端
常用中间件-OAuth2_第103张图片
常用中间件-OAuth2_第104张图片

@FeignClient("book-service")
public interface BookClient {

    @RequestMapping("/book/{bid}")
    Book getBookById(@PathVariable("bid") int bid);
}
@FeignClient("user-service")
public interface UserClient {
    
    @RequestMapping("/user/{uid}")
    User getUserById(@PathVariable("uid") int uid);
}

常用中间件-OAuth2_第105张图片
在borrowServiceImpl直接注入
常用中间件-OAuth2_第106张图片
启动类加入注解
常用中间件-OAuth2_第107张图片
此时openfeign访问没有携带token。还会出现之前的访问borrow服务出错。此时需要添加配置。

feign:
  oauth2:
  	#开启Oauth支持,这样就会在请求头中携带Token了
    enabled: true
    #同时开启负载均衡支持
    load-balanced: true

常用中间件-OAuth2_第108张图片
重新启动borrow-service,此时接口调用带有token,则访问成功
常用中间件-OAuth2_第109张图片
在这里插入图片描述
但是每次访问一次就需要校验一次,若用户多了,服务器压力就很大。

3.5 使用JWT存储Token

官网https://jwt.io
常用中间件-OAuth2_第110张图片
JWT令牌的格式如下:
常用中间件-OAuth2_第111张图片

常用中间件-OAuth2_第112张图片
在这里插入图片描述
Base64不是加密算法,只是一种信息的编码方式。

  public void test(){
      String str = "你们可能不知道只用20万赢到578万是什么概念";
    	//Base64不只是可以对字符串进行编码,任何byte[]数据都可以,编码结果可以是byte[],也可以是字符串
      String encodeStr = Base64.getEncoder().encodeToString(str.getBytes());
      System.out.println("Base64编码后的字符串:"+encodeStr);
  
      System.out.println("解码后的字符串:"+new String(Base64.getDecoder().decode(encodeStr)));
  }

常用中间件-OAuth2_第113张图片
常用中间件-OAuth2_第114张图片
这里使用最简单的一种方式,对称密钥,对验证服务器进行修改:

@Bean
public JwtAccessTokenConverter tokenConverter(){  //Token转换器,将其转换为JWT
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setSigningKey("lbwnb");   //这个是对称密钥,一会资源服务器那边也要指定为这个
    return converter;
}

@Bean
public TokenStore tokenStore(JwtAccessTokenConverter converter){  //Token存储方式现在改为JWT存储
    return new JwtTokenStore(converter);  //传入刚刚定义好的转换器
}

常用中间件-OAuth2_第115张图片

@Resource
TokenStore store;

@Resource
JwtAccessTokenConverter converter;

private AuthorizationServerTokenServices serverTokenServices(){  //这里对AuthorizationServerTokenServices进行一下配置
    DefaultTokenServices services = new DefaultTokenServices();
    services.setSupportRefreshToken(true);   //允许Token刷新
    services.setTokenStore(store);   //添加刚刚的TokenStore
    services.setTokenEnhancer(converter);   //添加Token增强,其实就是JwtAccessTokenConverter,增强是添加一些自定义的数据到JWT中
    return services;
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints
            .tokenServices(serverTokenServices())   //设定为刚刚配置好的AuthorizationServerTokenServices
            .userDetailsService(service)
            .authenticationManager(manager);
}

常用中间件-OAuth2_第116张图片
常用中间件-OAuth2_第117张图片
重启auth-service服务,通过密码模式获取token,此时返回的token是JWT令牌。可对其进行Base64解码
常用中间件-OAuth2_第118张图片
生成的access_token用base64解码查看,需要在".“前后分开解码。
第一段为jwt
常用中间件-OAuth2_第119张图片
第二段用户信息
常用中间件-OAuth2_第120张图片

然后对资源服务器进行配置;
常用中间件-OAuth2_第121张图片

security:
  oauth2:
    resource:
      jwt:
        key-value: lbwnb #注意这里要跟验证服务器的密钥一致,这样算出来的签名才会一致

常用中间件-OAuth2_第122张图片
然后在book-service,user-service都进行以上相应的修改,修改完重启对应服务,并加上token进行访问
常用中间件-OAuth2_第123张图片
user,book服务访问正常
常用中间件-OAuth2_第124张图片
常用中间件-OAuth2_第125张图片

你可能感兴趣的:(SpringCloud,Alibaba,中间件)