springbootSpring Cloud

OAuth2 协议与微服务安全

2020-07-13  本文已影响0人  乌鲁木齐001号程序员

OAuth2 角色 & 流程

OAuth2 角色 & 流程.png
角色
流程

Spring Security 中 OAuth2 的相关类

org.springframework.security.authentication.AuthenticationManager
org.springframework.security.core.Authentication

认证服务器 | 配置步骤

大致思路

通过配置,要让认证服务器知道:
1) 哪些客户端应用会访问我
2) 哪些用户是合法用户
3) 谁能找我验证这个令牌

pom 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
认证服务器配置
package com.lixinlei.security.auth.oauth2.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
//import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;

/**
 * 对认证服务器的配置要包括:
 * 1.哪些客户端应用会访问我
 * 2.哪些用户是合法用户
 * 3.谁能找我验证这个令牌
 */
//@EnableJdbcHttpSession
@Configuration
@EnableAuthorizationServer // 当前应用是作为认证授权服务器存在的
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 用来告诉认证服务器,合法的用户有哪些
     * 在 {@link OAuth2WebSecurityConfig#authenticationManagerBean()} 中暴露出来的 Bean
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 在 {@link OAuth2WebSecurityConfig#passwordEncoder()} 中暴露出来的 Bean
     */
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 自己的实现,合法用户的来源:{@link UserDetailsServiceImpl}
     */
    @Autowired
    private UserDetailsService userDetailsService;

//    @Autowired
//    private DataSource dataSource;

//    @Bean
//    public TokenStore tokenStore() {
//        return new JdbcTokenStore(dataSource);
//    }

    /**
     * 1. 哪些客户端应用会访问我(认证服务)
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        clients.jdbc(dataSource);
        clients.inMemory()
                .withClient("orderApp") // 可以让哪个客户端应用访问我这个认证服服务
                .secret(passwordEncoder.encode("123456")) // 客户端应用的密码
                .scopes("read", "write") // 将应用 orderApp 与它的权限挂钩,相当于 ACL,意思是 orderApp 拿着这个 Token 访问 order-server 能干什么
                .accessTokenValiditySeconds(3600) // 发出去的 Token 的有效期,单位:s
                .resourceIds("order-server") // 发出去的 Token 可以访问哪些资源服务器
                .authorizedGrantTypes("password") // OAuth2 有四种授权方式:password

                .and()

                // 配置一个资源服务器
                .withClient("orderService")
                .secret(passwordEncoder.encode("123456"))
                .scopes("read")
                .accessTokenValiditySeconds(3600)
                .resourceIds("order-server")
                .authorizedGrantTypes("password");

    }

    /**
     * 2. 哪些用户是合法用户
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }


    /**
     * 3. 谁能找我验证这个令牌
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 填了一个权限表达式,意思是来验 Token 的请求一定是通过身份认证的,就是要带着用户名密码,
        // 要么是 orderApp:123456,要么是 orderService:123456
        security.checkTokenAccess("isAuthenticated()");
    }

}
认证服务器配置的支撑配置
package com.lixinlei.security.auth.oauth2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * 就是要配置 AuthenticationManager,让 AuthenticationManager 告诉认证服务器合法的用户有谁
 */
@Configuration
@EnableWebSecurity // 让安全配置生效
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 获取合法的用户信息的,需要自己实现一下这个接口,根据用户名获取用户信息
    @Autowired
    private UserDetailsService userDetailsService;

    // Spring Security 封装的加密工具
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 构建 AuthenticationManager
     * 通过 userDetailsService 获取用户信息,
     * 通过 passwordEncoder() 把获取到的用户密码,和用户输入的密码比对
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

    /**
     * 把上面配置的 AuthenticationManager 暴露成一个 Bean
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
自己实现的,合法用户的来源
package com.lixinlei.security.auth.oauth2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 正真的业务实现,这里的用户来源一定是要读数据库的,这里目前是硬编码了一个用户
     * @param username
     * @return UserDetails 封装了 Spring 所需要的用户信息
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return User
                .withUsername(username)
                .password(passwordEncoder.encode("123456")) // 模拟从数据库里拿出来的密码都是密文的
                .authorities("ROLE_ADMIN") // 理解成角色
                .build();
    }

}

认证服务器 | 发挥作用

向认证服务器发请求
认证服务器的响应
{
    "access_token": "d125244e-1472-435f-a7b4-3df6a159dde2",
    "token_type": "bearer",
    "expires_in": 3550,
    "scope": "read write"
}

资源服务器

资源服务器要知道的四件事

资源服务器 | 配置

大致思路
资源服务器本身的配置
package com.lixinlei.security.order.oauth2.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;

@Configuration
/**
 * 开启 @EnableResourceServer 之后,就是告诉 order 这个服务,它本身是作为 OAuth2 中的资源服务器;
 * 开启 @EnableResourceServer 之后,默认所有发往 order 这个服务的 HTTP 请求,
 * order 服务都会在请求的 HTTP Header 中找 Token,如果找不到 Token,就不让过,当需要设置哪些请求要 Token,哪些不要的时候,就可以覆盖方法:
 * configure(HttpSecurity http)
 */
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 申明我这个 order 服务就是在认证服务器中配置的 order-server,给 orderApp 客户端应用发的 Token 只能访问我;
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("order-server");
    }

    /**
     * 除了 /haha 这个请求不需要 Token,其他的请求都需要 Token
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/haha").permitAll()
                .anyRequest().authenticated();
    }

}
验证 Token 的时候,需要的安全信息配置
package com.lixinlei.security.order.oauth2.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.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;

@Configuration
@EnableWebSecurity
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 让 order 服务知道自己在 OAuth2 协议中是什么服务器,
     * orderService 是在认证服务器中配置的名字;
     * @return
     */
    @Bean
    public ResourceServerTokenServices tokenServices() {
        RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId("orderService");
        tokenServices.setClientSecret("123456");
        tokenServices.setCheckTokenEndpointUrl("http://localhost:9090/oauth/check_token");
        return tokenServices;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        OAuth2AuthenticationManager authenticationManager = new OAuth2AuthenticationManager();
        authenticationManager.setTokenServices(tokenServices());
        return authenticationManager;
    }

}
改造微服务原本的接口
@PostMapping
public OrderInfo create(@RequestBody OrderInfo info, @AuthenticationPrincipal String username) {
    log.info("user is " + username);
//      PriceInfo price = restTemplate.getForObject("http://localhost:9060/prices/"
//                + info.getProductId(), PriceInfo.class);
//      log.info("price is " + price.getPrice());
    return info;
}

资源服务器 | 生效

上一篇 下一篇

猜你喜欢

热点阅读