当前位置:   article > 正文

搭建微服务下统一认证授权服务,鉴权客户端大致流程(基于无状态)_微服务统一认证与授权

微服务统一认证与授权

1.简介

  • 基于无状态令牌(jwt)的认证方案,服务端无需保存用户登陆状态
  • 基于spring security框架 + oauth2协议 搭建
  • 基于spring cloud nacos,服务调用使用RestTemplate

前置知识:

  • jwt (也就是这里用到的token)

    • 就是一大串加密的字符串,用户的登陆状态都保存在里面,但这种字符串里面的数据不能被修改,一但修改校验一定会出错.这个字符串谁都可以解析,所以里面一定一定不能存放用户敏感信息.
    • 缺点是: 这种token一旦被颁发就无法销毁(俗称退出登陆状态),所以尽量把token有效期设置短一点,refresh_token相对设置长一点.有很多人把颁发过的token在服务端持久化,然后每次登陆去服务端校验token是否存在,这样注销就变为在服务端删除掉token,硬生生把无状态认证变成有状态,一般不建议.
    • 优点: 无状态服务端性能压力小,跨语言
  • spring security

    • 认证授权框架,就是整合之后默认帮你默认实现了一堆过滤器链,
    • 分享个图
  • oauth2协议

    • 协议协议,所谓协议就是双方规定好的交互方式,我跟你要怎样去交互,在oauth2协议的规范下的每一位’成员’都有其自己的一些’名词’ ,什么资源服务器,客户端,资源拥有者…,这些名词也可能是’多义词‘,既是资源服务器有事认证服务器…, 这些各个‘名词’要交互,所以它们之间之间也有规范好的交互方式,什么授权码模式,密码式…
    • 一般是拿来做第三方应用授权,但也可以拿来做SSO

采用的认证授权方案大致流程为:

  • 需要登陆的用户统一去访问认证授权服务器,由它统一颁发token,然后每个需要受保护的微服务都添加鉴权客户端依赖来作为一个资源服务器,当请求带着token访问资源服务器时,由鉴权客户端去校验解析token,得到当前用户登陆状态信息
  • 这里网关只是转发请求,先不搭建.

2.开始搭建

2.1 创建两个基础空springboot项目


介绍:

  • authentication-server: 统一认证授权服务,说白就是颁发校验刷新token…的服务
  • auth-client: 鉴权客户端,说白就是校验token的通用工具依赖

2.2 编写 authentication-server

2.2.1 pom导入依赖

        <!-- spring cloud  -->
      <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>



     <!-- 已包含spring security +  spring security ouath2 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        
        
         <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        
           <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

     <!--服务注册与服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

2.2.2 在数据创建表

一般需要这几张表:

  • 主要用于存放oauth2协议中的client信息,颁发的token,refresh toekn等信息
  • 由于是基于token的认证方案,一般只用到oauth_client_details 这张表,并不会使用到其他表,比如oauth_access_token这张表,它是在基于session认证方案时存放颁发出去的token(即服务端保存登陆状态)
  • 具体表信息查看:
  • http://www.andaily.com/spring-oauth-server/db_table_description.html

创建表sql脚本:

  • 然后在application.yml配置下mysql地址
-- 客户端应用注册详情
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY, -- 客户端应用的账号
  resource_ids VARCHAR(256),		-- 客户端应用可访问的资源服务器列表,(空代表所有资源服务器都可以访问)
  client_secret VARCHAR(256),	-- 客户端应用的密码
  scope VARCHAR(256),	-- 资源服务器拥有的所有权限列表 (get add delete update)  
  authorized_grant_types VARCHAR(256), -- 客户端支持的授权码模式列表
  web_server_redirect_uri VARCHAR(256), -- 授权码模式,申请授权码后重定向的uri.
  authorities VARCHAR(256),
  access_token_validity INTEGER,   -- 设置颁发token的有效期
  refresh_token_validity INTEGER,  -- 颁发refresh_token的有效期(不设置不会同时颁发refresh_token)
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)     -- 设置为true,授权码模式下自动授权
);

create table oauth_client_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);

-- 基于session认证时,存放颁发的token
create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication BLOB,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication BLOB
);

-- 授权码模式下,存放颁发的授权码
create table oauth_code (
  code VARCHAR(256), authentication BLOB
);

create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt DATETIME,
	lastModifiedAt DATETIME
);

-- 基于session认证时,并且采用分布式session
CREATE TABLE SPRING_SESSION (
   PRIMARY_ID CHAR(36) NOT NULL,
   SESSION_ID CHAR(36) NOT NULL,
   CREATION_TIME BIGINT NOT NULL,
   LAST_ACCESS_TIME BIGINT NOT NULL,
   MAX_INACTIVE_INTERVAL INT NOT NULL,
   EXPIRY_TIME BIGINT NOT NULL,
   PRINCIPAL_NAME VARCHAR(100),
   CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
 );

 CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
 CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
 CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

 CREATE TABLE SPRING_SESSION_ATTRIBUTES (
  SESSION_PRIMARY_ID CHAR(36) NOT NULL,
  ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
  ATTRIBUTE_BYTES BLOB NOT NULL,
  CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
  CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
 );

 CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

在oauth_client_details表配置client客户端信息

  • 因为去认证中心请求获取token的客户端只能是之前在认证中心这注册过的客户端,不可能说你一个陌生客户端都让来请求token
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('burukeyou-user', NULL, '$2a$10$Qg.q5o1ZBVy/7TrV107u6ORr1Vyl.wm8AGU5tFcbpw2zPQuuxh82C', 'get,add', 'password,authorization_code,refresh_token', NULL, NULL, 60000, 300, NULL, NULL);

  • 1
  • 2
  • 这些字段意义基于oauth2协议,不在详述

2.2.3 启动nacos并把authentication-server注册到nacos

省略(对实现最终效果影响不大):

  • 大致流程: 启动nacos,配置application.yml中nacos的地址,程序入口类添加@EnableDiscoveryClient 注解

2.2.4 编写自定义UserDetailsService

  • 这个类就是用户来请求获取token时,根据该用户的username加载用户信息返回即可,而校验用户账号密码的操作之后会被AuthenticationManager调用一个认证器去校验用户账号密码完成
@Component
@Slf4j
public class Ouath2UserDetailService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Assert.notNull(username,"aut username can not be null");

        // (省略.......)创建UserTokenVo,它就是 要继承UserDetails接口,添加稍后要存放到token里的字段到这个类(稍后自定义实现token增强器),
        UserTokenVo user = new UserTokenVo();
     
        // (省略.......) (自定义去数据库根据username查找数据库用户表对应的UmsUser对象)
        UmsUser umsUser =  userService.findByUSername..........省略
                

        if (umsUser == null)
            return null;

        // 把umsUser的属性值复制给UserTokenVo
        省略.......
        
        // 返回  UserTokenVo
        return user;
    }
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2.2.5 编写WebSecurityConfigurerAdapter

配置认证中心的spring security

作用:

  • 之前编写的UserDetailService在这里配置注入到AuthenticationManager中
@EnableWebSecurity
@Configuration
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private  Ouath2UserDetailService ouath2UserDetailService;

    // 重新注入认证管理器
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    // 注入密码加密
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(ouath2UserDetailService) // 使用自定义方式加载用户信息
                .passwordEncoder(passwordEncoder());
    }
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

2.2.6 编写token增强器TokenEnhancer

/**
 *  增强颁发的token的携带信息
 *
 *  
 *
 */
public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        // 这个UserTokenVo就是之前UserDetial返回的对象
        //从那获取要增强携带的字段
        UserTokenVo user = (UserTokenVo) authentication.getPrincipal();

        final Map<String, Object> additionalInfo = new HashMap<>();

        //添加token携带的字段
        additionalInfo.put("id", user.getId());
        additionalInfo.put("nickname", user.getNickname());
        additionalInfo.put("avatar", user.getAvatar());
        additionalInfo.put("description", user.getDescription());
        additionalInfo.put("blog_address", user.getBlog_address());

        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
        token.setAdditionalInformation(additionalInfo);

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);

        return accessToken;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

2.2.7 编写认证授权服务器AuthorizationServerConfigurerAdapter

作用:

  • 配置认证中心的oauth2组件,比如自定义的TokenEnhancer,自定义的UserDetailService,重新配置的AuthenticationManager,之前创建数据库表的数据源
  • 配置这个类后已经帮我们实现了oauth2协议的大部分操作和持久化配置(之前创建的表),什么颁发token,校验token,刷新token,这些个功能都暴露成固定的请求接口供外部调用

    • /oauth/token 颁发和刷新token (具体实现见源码TokenEndpoint这个类)
    • /oauth/token_key 校验token
/** ouath2 认证服务器配置
 *
 *
 *
 */
@Configuration
@EnableAuthorizationServer // 开启认证服务器
public class Ouath2AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;

    private final DataSource dataSource;

    private Ouath2UserDetailService ouath2UserDetailService;

    public Ouath2AuthServerConfig(AuthenticationManager authenticationManager, DataSource dataSource,
                                  Ouath2UserDetailService ouath2UserDetailService
                               ) {
        this.authenticationManager = authenticationManager;
        this.dataSource = dataSource;
        this.ouath2UserDetailService = ouath2UserDetailService;
    }

   
    /** =================================配置=============================================================
     *  1.
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource); //这里就是配置之前sql脚本的数据源
    }


    /**
     *      2.认证服务器安全配置
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //基于session认证会用到
        /* security//.checkTokenAccess("isAuthenticated()")
                // 认证中心往外面暴露的一个用来获取jwt的SigningKey的服务/oauth/token_key,但我选择在每个资源服务器本地配置SigningKey
                //.tokenKeyAccess("isAuthenticated()") */
    }


    /**
     *  3.      配置customTokenEnhancer,自定义UserDetailService,token存储策略
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 将增强的token设置到增强链中
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        enhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), jwtAccessTokenConverter()));

        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager)
                // //刷新token的请求会用用到
                .userDetailsService(ouath2UserDetailService)
                .tokenEnhancer(enhancerChain);
    }
}
    //========================注入=====================================
 // 更改存储token的策略,默认是内存策略,修改为jwt
    @Bean
    public TokenStore tokenStore() {
        //return new JdbcTokenStore(dataSource);  //基于session认证
        return new JwtTokenStore(jwtAccessTokenConverter());  //基于token认证
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jat = new JwtAccessTokenConverter();
        jat.setSigningKey("909090"); // jwt使用这个key来签名,验证token的服务也使用这个key来验签
        return jat;
    }

    // 添加自定义token增强器实现颁发额外信息的token,因为默认颁发token的字段只有username和ROLE
    @Bean
    public TokenEnhancer customTokenEnhancer() {
        return new CustomTokenEnhancer(); // 自定义实现
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

2.2.8 测试认证授权服务器

打开postman申请token:

  • 流程为用户tangtanwei使用burukeyou-user客户端信息去认证服务器获取token

1.点击authorization,使用basic-autu,设置申请令牌的客户端应用信息

  1. 点击body,输入用户账号密码,还有选择授权模式grant_type,还有请求权限范围scope

如果用户账号正确,basic-ahth页设置正确将会返回如下信息:

复制access_token去jwt官网解析
地址:

https://jwt.io/

2.3 编写鉴权客户端 auth-client

目的:

  • 每个资源服务器可能都要实现校验token的逻辑,不可能每次都写一遍,抽象出来做成通用依赖,每次要使用时添加依赖即可

2.3.1 pom.xml导入依赖


 <!-- security ouath2 -->   
        <! -- 核心依赖  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!--导入配置文件处理器,配置文件进行绑定就会有提示-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.3.2 编写UserAuthenticationConverter和ResourceServerTokenServices

作用:

  • 默认情况下,资源服务器解析后的token不会包含之前颁发时额外携带的字段信息得重写UserAuthenticationConverter自定义解析实现,将token转化为用户信息
public class CustomUserAuthenticationConverter implements UserAuthenticationConverter {

    private static final String N_A = "N/A";

    @Override
    public Authentication extractAuthentication(Map<String, ?> map) {
        // 这个map就是存放token各个字段对应的信息
        if (!map.containsKey(USERNAME))
            return null;

        // CurrentUserInfo自定义当前登陆用户对象,存放token解析出来的信息(省略......)
        CurrentUserInfo user = new CurrentUserInfo();
        user.setId((String) map.get("id"));
        user.setUsername((String) map.get(USERNAME));
        user.setNickname((String) map.get("nickname"));
        user.setAvatar((String) map.get("avatar"));

        // 有权限信息就格式化权限信息
        if (map.containsKey("authorities") && map.get("authorities") != null){
            Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
            user.setAuthorities(authorities);
            return new UsernamePasswordAuthenticationToken(user, N_A,authorities);
        }else {

            return new UsernamePasswordAuthenticationToken(user, N_A,null);
        }
    }

    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
        Object authorities = map.get(AUTHORITIES);
        if (authorities instanceof String) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
        }
        if (authorities instanceof Collection) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
                    .collectionToCommaDelimitedString((Collection<?>) authorities));
        }else if (authorities == null){

        }
        throw new IllegalArgumentException("Authorities must be either a String or a Collection");
    }


    // 复制 DefaultUserAuthenticationConverter
    @Override
    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        Map<String, Object> response = new LinkedHashMap<String, Object>();
        response.put(USERNAME, authentication.getName());
        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
            response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }
        return response;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
编写ResourceServerTokenServices 中使用
  • 这个类依赖于三个对象TokenStore,DefaultAccessTokenConverter,JwtAccessTokenConverter,稍后在资源服务器配置类注入

@Data
public class CustomTokenServices implements ResourceServerTokenServices {

    private TokenStore tokenStore; //认证策略,基于内存,jdbc,jwt,redis.....

    private DefaultAccessTokenConverter defaultAccessTokenConverter;

    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Override
    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        //从token中解析得到authentication
        OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
        
        //
        Map<String, ?> map = jwtAccessTokenConverter.convertAccessToken(readAccessToken(accessToken), oAuth2Authentication);
        //根据重新设置默认转换器再去获取authentication (稍后在资源服务器配置类注入CustomUserAuthenticationConverter)
        return defaultAccessTokenConverter.extractAuthentication(map);
    }

    @Override
    public OAuth2AccessToken readAccessToken(String accessToken) {
        return tokenStore.readAccessToken(accessToken);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2.3.3 编写资源服务器ResourceServerConfigurerAdapter

作用:

  • 将刚下重新编写的oauth2组件注入到oauth2体系中

先编写一个添加auth-client依赖时,资源服务器的自动属性配置类,把功能属性配置暴露出去

@Data
@Component
@ConfigurationProperties(prefix = "custom.ouath2.client")
public class AuthClientProperties {

     //与签名jwt时使用的密钥,否则解析token失败
    private String signingKey;

    //声明资源服务器名称
    //在oauth_client_details表里的resource_ids字段就是那个客户端可访问的资源服务器列表.
    private String resourceId; 

    // 资源服务器自定义放行的请求列表
    private List<String> ignoreUrls;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

配置资源服务器:

/**
 *  资源服务器配置
 *
 * @author burukeyou
 *
 */
@Slf4j
@Configuration
@EnableResourceServer  //注册为资源服务器
@ComponentScan("burukeyou.auth.authClient")
public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter {

    // spring security 里的AuthenticationEntryPoint,就是自定义认证失败时的处理方式,默认是返回它自己的错误信息
    //实现AuthenticationEntryPoint接口即可(省略.........)
    @Autowired
    private CustomAuthenticationEntryPoint baseAuthenticationEntryPoint;

    // 自定义配置类
    @Autowired
    private AuthClientProperties authClientProperties;

    // 注入密码加密
    /*@Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }*/


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        // 放行 swagger ui (有整合swagger就放行这些请求吧)
        http.authorizeRequests().antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
                "/swagger-resources","/swagger-resources/configuration/security",
                "/swagger-ui.html","/course/coursebase/**","/webjars/**","/api/**/v2/api-docs").permitAll();

        // 根据自定义配置url放行
        if (authClientProperties.getIgnoreUrls() != null){
            for(String url: authClientProperties.getIgnoreUrls()){
                http.authorizeRequests().antMatchers(url).permitAll();
            }
        }
        // 其他请求均需要token才能访问
        http.authorizeRequests().anyRequest().authenticated();
       // http.authorizeRequests().anyRequest().permitAll();

    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
      if (authClientProperties.getResourceId() != null)
            resources.resourceId(authClientProperties.getResourceId());

        // 这里的签名key 保持和认证中心一致
        if (authClientProperties.getSigningKey() == null)
            log.info("SigningKey is null cant not decode token.......");

        //
        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());

        //
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(authClientProperties.getSigningKey()); //设置解析jwt的密钥
        converter.setVerifier(new MacSigner(authClientProperties.getSigningKey()));

        //
        CustomTokenServices tokenServices = new CustomTokenServices();

        // 在CustomTokenServices注入三个依赖对象
        tokenServices.setTokenStore(new JwtTokenStore(converter)); //设置token存储策略
        tokenServices.setJwtAccessTokenConverter(converter);
        tokenServices.setDefaultAccessTokenConverter(accessTokenConverter);
        
        resources.tokenServices(tokenServices)
            .authenticationEntryPoint(baseAuthenticationEntryPoint);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

在编写一个工具类方便以后获取到当前登陆用户信息

public class AuthUtils {

    private AuthUtils(){}

    /**
     *  get current login user info
     * @return
     */
    public static CurrentUserInfo getCurrentUser(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return  authentication != null ? (CurrentUserInfo)authentication.getPrincipal():null;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.4 实践

  • 至此,有了authentication-server,auth-client之后骨架便完成
  • 接下来便是应用

2.5 创建一个新的springboot项目

大致流程:

  1. 在pom.xml中导入之前创建的auth-client依赖
        <!-- auth - client-->
        <dependency>
            <groupId>com.burukeyou</groupId>
            <artifactId>auth-client</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 在spring boot入口类添加@ComponentScan组件位置,否则扫描不到auth-client的组件,应为springboot默认扫描痛入口类的当前目录下
  • 也可以再添加个@EnableGlobalMethodSecurity(prePostEnabled = true) //开启方法级别的权限拦截

3.编写application.yml自定义资源服务器配置

  • 就是auth-client下的AuthClientProperties自动属性配置类

custom:
  ouath2:
    client:
      # jwt的密钥
      signingKey: 909090
      resourceId: ${spring.application.name}
      # 放行的url
      ignoreUrls:
        - /oauth/**
        - /user/**
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.然后随便写个controller
比如:

@RestController
@RequestMapping("/search")
@Api(value = "全局搜索接口",description = "...")
public class GlobalController {

    @GetMapping("/a")
    @PreAuthorize("isAuthenticated()")
    public String get(@AuthenticationPrincipal CurrentUserInfo userInfo){
        return "1";
    }

    @GetMapping("/b")
    @ApiOperation(value = "查找2",notes = "xx")
    public String get02(@AuthenticationPrincipal CurrentUserInfo userInfo){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "2";
    }

    @GetMapping("/c")
    public String get03(@AuthenticationPrincipal CurrentUserInfo userInfo){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        return "3";
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

随便调用这些接口可发现除/oauth/**和/user/**路径被放行,其他均需要校验token才能访问

再来比如访问 /search/c 接口

查看此时 /search/c 接口 debug信息

  • 这个我们发现通过@AuthenticationPrincipal注解或者是自定义实现的AuthUtils工具类都可以获取到当前登陆用户状态.

  • 如有理解错误,望指出,谢谢.

打赏

如果觉得文章有用,你可鼓励下作者

在这里插入图片描述


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/701674
推荐阅读
相关标签
  

闽ICP备14008679号