1、分布式系统认证需求

分布式每个服务都会有认证授权的需求。如果每个服务都些认证授权的逻辑就会很冗余。所以需要把认证授权进行抽取,进行统一认证授权。

认证授权服务,需要支持多种认证授权的方式,如用户名密码、短信验证码、二维码等等。同时要给其他服务提供其他应用接入的的认证。

2、分布式系统认证方案

1、选型

  • 基于session的认证方式:存在session共享的问题。方案很多,常用的直接使用redis进行存储,spring boot 也提供了相关的插件。缺点是不同的客户端cookie机制可能不一样。
  • 基于token认证方式:认证成功后,服务端给客户端返回一个token,包含用户信息等,这样客户端自己保存,每次请求带着token即可。缺点是占贷款、每次请求都需要签名验证。也是现阶段常用的验证方式。

2、OAuth2.0介绍

 OAuth2.0是开放授权的标准,允许用户授权第三方应用访问他们存储在另外的福提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。(和OSS的区别,OSS使用于系统内部的模块认证、OAuth2.0适合提供给第三方认证)。

例如用户需要用微信登陆某网站,网站需要从微信中获取身份信息就认为用户认证成功。微信在用户同意的情况下,给网站生成token,网站使用token从微信获取用户信息。

  • 客户段:例子中就是网站,认证服务器会颁给客户端标识和密钥
  • 资源拥有者:用户本人
  • 认证服务器:微信的OAuth2.0认证服务器,认证客户段和资源拥有者两部分
  • 资源服务器:微信的资源服务器,存放用户信息 

三、Spring cloud security OAuth2.0

是spring security提供的OAuth2.0功能,整体包含了2个服务:

1、授权服务

  • TokenEndpoint:服务访问令牌请求。默认路径:/oauth/token
  • AuthorizationEndpoint:服务认证请求。默认路径:/oauth/authorize

2、资源服务

  • OAuth2AuthorztionProcessingFilter:用来对请求给出的身份令牌解析鉴权

3、认证流程如下:

  • 客户端请求UAA授权服务进行认证
  • 认证通过后UAA服务办法令牌
  • 客户端携带token令牌请求资源服务
  • 资源服务校验令牌的合法行,合法则返回资源信息

四、认证服务器配置

1、服务器配置

package com.mg.uaa.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;


/**
 * 授权服务
 * 1、既然要完成验证,就需要知道客户端信息从哪里来,因此配置客户端信息
 * 2、既然要办法token,需要定义token相关的endpoint,并且知道怎么样存取token和客户端支持那些类型的token
 * 3、既然报漏了endpoint,就需要对这endpoint进行安全约束
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer implements AuthorizationServerConfigurer {

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 用来配置令牌的安全约束
     * @param authorizationServerSecurityConfigurer
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
        authorizationServerSecurityConfigurer
                //  /oauth/token_key 公开的
                .tokenKeyAccess("permitAll()")
                // /oauth/check_token 公开的
                .checkTokenAccess("permitAll()")
                //表单申请令牌
                .allowFormAuthenticationForClients();
    }

    /**
     * 客户端详情,对客户端身份进行鉴权
     * 指定支持的客户端
     * 该配置负责查询客户端,有如下几个属性:
     * 1、clientId:客户端标识
     * 2、secret:客户端密匙
     * 3、scope:用来显示客户端的访问范围
     * 4、authorizedGrantTypes:客户端可以使用的授权类型
     * 5、authorities:客户端的使用权限,粒度更小
     * @param clientDetailsServiceConfigurer
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
        clientDetailsServiceConfigurer.inMemory()
                //客户端名称
                .withClient("c1")
                //密码
                .secret(new BCryptPasswordEncoder().encode("123"))
                //资源列表
                .resourceIds("res1")
                //支持的认证类型
                .authorizedGrantTypes("implicit","refresh_token", "password", "authorization_code", "client_credentials")
                //授权范围: 允许使用的权限例如 user_base_info
                .scopes("all")
                //token方式的时候,是否跳转到授权页面
                .autoApprove(false)
                //加签证回调地址
                .redirectUris("http://www.baidu.com");
    }

    /**
     * 来申请的令牌的访问端点和令牌的服务
     * @param authorizationServerEndpointsConfigurer
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception {
        /**
         * 1、authenticationManager:资源授权者你用密码授权类型的时候 使用
         * 2、userDetailsService:?自己实现的userDetailsService
         * 3、authorizationCodeServices:授权码模式
         * 4、tokenGranter:自定义授权模式
         *
         * 配置授权端点。用户可以自己配置,框架已给出默认的:
         * 1、/oauth/authorize:授权端点
         * 2、/oauth/token:令牌端点
         * 3、/oauth/confirm_access:授权服务错误信息端点
         * 4、/oauth/error:授权服务错误信息端点
         * 5、/oauth/check_token:用户资源服务访问令牌解析端点
         * 6、/oauth/token_key:提供公有密匙的端点(使用JWT情况,就是提供非对称加密的公匙)
         */
        authorizationServerEndpointsConfigurer
                //密码模式需要
                .authenticationManager(authenticationManager)
                //授权吗模式
                .authorizationCodeServices(authorizationCodeServices)
                //令牌管理服务
                .tokenServices(tokenService())
                //post提交
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        //设置授权码模式的授权码如何 存取,暂时采用内存方式
        return new InMemoryAuthorizationCodeServices();
    }

    /**
     * 令牌服务
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service=new DefaultTokenServices();
        //客户端信息的服务
        service.setClientDetailsService(clientDetailsService);
        //是否刷新令牌
        service.setSupportRefreshToken(true);
        //存储策略
        service.setTokenStore(tokenStore);
        //有效时间
        service.setAccessTokenValiditySeconds(7200);
        //刷新时间
        service.setRefreshTokenValiditySeconds(259200);
        return service;
    }
}
package com.mg.uaa.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author Administrator
 * @version 1.0
 **/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //认证管理器
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/r1").hasAnyAuthority("p1")
                .antMatchers("/login*").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();
    }
}
package com.mg.uaa.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * @author Administrator
 * @version 1.0
 **/
@Configuration
public class TokenConfig {

    @Bean
    public TokenStore tokenStore() {
        //1、InMemoryTokenStore:所有的token都会保存在内存中
        //2、JdbcTokenStore:token保存在数据库中
        //3、JwtTokenStore:基于JWT生成的token,因为信息都保存在token中,所以不需要存储,但是想要撤销十困难,通常用来处理声明周期短的token
        return new InMemoryTokenStore();
    }
}

 

 ​​模式测试:

  • 授权码模式:(比较安全的模式,微信授权使用的就是这种)
    • /uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
    • /uaa/oauth/token?
      client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://www.baidu.com
  • 简化模式:直接去跳换页面,并携带token,用于纯静态页面的客户端
    • /uaa/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com
  • 密码模式,需要带着用户名和密码,用于自己开发的前段
    • /uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123
  • 客户端模式,相对更简单,只要传递客户端凭证即可,对客户端完全信任
    • /uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials

2、资源服务器配置

package com.mg.order.conifg;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;

/**
 * 服务资源的配置
 */
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "res1";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
                //资源ID
                .resourceId(RESOURCE_ID)
                //验证令牌的服务
                .tokenServices(tokenService())
                .stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**")
                //令牌的 scope 必须是 all
                .access("#oauth2.hasScope('all')")
                .and().csrf().disable()
                //不实用 session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    /**
     * 远程验证服务令牌
     * @return
     */
    @Bean
    public ResourceServerTokenServices tokenService() {
        //使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
        RemoteTokenServices service=new RemoteTokenServices();
        service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
        service.setClientId("c1");
        service.setClientSecret("123");
        return service;
    }
}
package com.mg.order.conifg;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author Administrator
 * @version 1.0
 **/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
//                .antMatchers("/r/r1").hasAuthority("p2")
//                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
        ;


    }
}

测试:

 这样就可以访问了。总结

1、认证服务器:主要用于token的办法,同时规定哪些客户端、哪些资源可以访问

2、资源服务器:需要配置资源ID、scope等

3、token生成也可以使用JWT,JWT本身就包含了客户信息,所以不用每次都要远程调用去验证用户信息。

Logo

更多推荐