Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)

接口授权认证

用Oauth2.0 管理微服务中的开放接口,对接口进行授权认证:

在Spring Cloud需要使用oauth2来实现多个微服务的统一认证授权,通过向OAUTH服务发送某个类型的grant type进行集中认证和授权,从而获得access_token,而这个token是受其他微服务信任的,我们在后续的访问可以通过access_token来进行,从而实现了微服务的统一认证授权。
客户端根据约定的ClientID、ClientSecret、Scope来从Access Token URL地址获取AccessToken,并经过AuthURL认证,用得到的AccessToken来访问其他资源接口。
注: Spring Cloud oauth2 需要依赖Spring security 在引入Oauth框架是需要引入Spring security

所以别人在调用我们接口的时候,首先需要在我们平台上申请appId和appKey,这里我们需要搭建一个门户网站供别人使用,实例里面就直接写死了。

微服务安全框架 SpringBootSecurity,Oauth2角色划分

1、Resource Server:被授权访问的资源
2、Authotization Server:OAUTH2认证授权中心
3、Resource Owner: 用户
4、Client:使用API的客户端(如Android 、IOS、web app)

OAuth2四种授权方式:
1、授权码(认证码)模式(Authorization code):用在客户端与服务器端应用之间的授权,response_type=code
2、简化(隐形)模式(Impilict):应用在移动app或者web app(在移动设备上的,如在手机中调用微信来进行认证授权),response_type=token
3、密码模式(Resource Owner Password Credentials):应用直接都是受信的,如都是由同一家公司开发,grant_type=password
4、客户端模式(Client Credentials):用在应用API访问,grant_type=client_credential
一般情况下,1和3应用比较多,2都是应用在移动端,4应用不多

架构:

客户端不能直接调用订单服务的,需要走网关,然后进入到订单服务里面。后期服务比较多时候,需要授权认证。

添加一层 Oauth2.0授权中心 ,对于订单服务、商品服务 都要做accessToken验证的 。 通过网关之后还有一层认证授权中心。

客户端先去获取accessToken,而accessToken生成于Oauth2.0认证授权中心。即为: 客户端到认证授权中心,通过appId和appSerect拿到accessToken。

验证accessToken是否有效等等也是通过它实现的。

认证授权中心是管理accessToken相关的

过程:客户端携带appid和授权码去Oauth2.0认证授权中心,获取accessToken。

客户端携带accessToken去访问,经过网关,网关转发到订单服务,然后去Oauth2.0认证授权中心去验证accessToken。

注:客户端代表某一个合作机构
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第1张图片

环境搭建

要先搭建认证授权中心,然后在搭建资源服务器

1、搭建认证授权中心

maven依赖

<parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.1.RELEASEversion>
    parent>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Finchley.M7version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-freemarkerartifactId>
        dependency>

        spring-boot 整合security -->
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
        dependency>
    dependencies>
    
    <repositories>
        <repository>
            <id>spring-milestonesid>
            <name>Spring Milestonesname>
            <url>https://repo.spring.io/libs-milestoneurl>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
        repository>
    repositories>

配置授权中心:AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer // 开启认证授权中心
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    //accessToken 有效期2小时
    private static final int ACCESSTOKENVALIDITYSECONDS = 7200;
    //刷新accessToken
    private static final int REFRESHTOKENVALIDITYSECONDS = 7200;

    /**
     * 添加商户信息
     * 配置appid、appKey、回调地址、token有限期
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //申请获取到appid和appKey 暂时写死
        clients.inMemory().withClient("client_1").secret(passwordEncoder().encode("123456"))
                //重定向地址   授权类型   可进行授权的列表
                .redirectUris("http://www.baidu.com").authorizedGrantTypes("password", "client_credentials", "refresh_token", "authorization_code").scopes("all")
                //accessToken有效期
                .accessTokenValiditySeconds(ACCESSTOKENVALIDITYSECONDS)
                //刷新accessToken
                .refreshTokenValiditySeconds(REFRESHTOKENVALIDITYSECONDS);
    }

    // 设置token类型
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager()).allowedTokenEndpointRequestMethods(HttpMethod.GET,
                HttpMethod.POST);
        //刷新令牌接口
        endpoints.authenticationManager(authenticationManager());
        endpoints.userDetailsService(userDetailsService());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        // 允许表单认证
        oauthServer.allowFormAuthenticationForClients();
        // 允许check_token访问
        oauthServer.checkTokenAccess("permitAll()");
    }

    @Bean
    AuthenticationManager authenticationManager() {
        AuthenticationManager authenticationManager = new AuthenticationManager() {

            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                return daoAuhthenticationProvider().authenticate(authentication);
            }
        };
        return authenticationManager;
    }

    @Bean
    public AuthenticationProvider daoAuhthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService());
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    // 设置添加用户信息,正常应该从数据库中读取
    @Bean
    UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
                .authorities("ROLE_USER").build());
        userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
                .authorities("ROLE_USER").build());
        return userDetailsService;
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        // 加密方式
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder;
    }
}

因为依赖于security框架!必须要配置security。强制要求配置,不然访问授权中心会直接报错

@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	// 拦截所有请求,使用httpBasic方式登陆
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin();
	}
}

可以看到这里appId、appKey、重定向地址都是写死的,应该都是放在数据库的
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第2张图片
启动类:

@SpringBootApplication
public class AppOauth2Server {
    public static void main(String[] args) {
        SpringApplication.run(AppOauth2Server.class, args);
    }
}

a.使用授权码code获取accessToken
访问 http://localhost:8080/oauth/authorize?response_type=code&client_id=client_1&client_secret=123456&redirect_uri=http://www.baidu.com在这里插入图片描述
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第3张图片
点击登录:如下图所示,应用client_1 是否接受资源授权? 接受 和 拒绝
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第4张图片
选择approve,点击授权按钮
在这里插入图片描述
跳转到了百度页面,连接后面跟的code的值就是授权码

使用授权码获取accessToken:
http://localhost:8080/oauth/token?grant_type=authorization_code&code=Nu6Uxk&redirect_uri=http://www.baidu.com&scope=all&client_id=client_1&client_secret=123456
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第5张图片
还可以验证token是否有效
http://localhost:8080/oauth/check_token?token=514ec7e6-b5cd-462a-a3db-2f6b8548df37
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第6张图片
也可以调用该接口对token进行刷新
http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=9da9bac7-27d9-45ad-9e77-7db2b05ce183&client_id=client_1&client_secret=123456
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第7张图片

// 设置token类型
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager()).allowedTokenEndpointRequestMethods(HttpMethod.GET,
                HttpMethod.POST);
        //重新设置userDetailsService  不然刷新accessToken时候会报错!!!
        endpoints.authenticationManager(authenticationManager());
        endpoints.userDetailsService(userDetailsService());
    }

b.使用密码方式获取accessToken
http://localhost:8080/oauth/token?grant_type=password&username=user_1&password=123456&client_id=client_1&client_secret=123456&scope=all
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第8张图片
可以看到,与刚才通过授权码获取到的accessToken是一样的。因为是否appId进行关联的。

小结: 密码模式想要获取到accessToken,不用authorization code . 使用用户名和密码,就可以获取到accessToken了。

用户名 和 密码要与配置的一致,实际项目时候用的是读数据库动态获取。
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第9张图片

2、搭建资源服务中心

maven依赖:

<parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.1.RELEASEversion>
    parent>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Finchley.M7version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-freemarkerartifactId>
        dependency>

        spring-boot 整合security -->
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
        dependency>


    dependencies>
    
    <repositories>
        <repository>
            <id>spring-milestonesid>
            <name>Spring Milestonesname>
            <url>https://repo.spring.io/libs-milestoneurl>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
        repository>
    repositories>

资源拦截配置config:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //对 /api/order请求进行拦截
        http.authorizeRequests().antMatchers("/api/order/**").authenticated();
    }
}

资源配置controller:

@RestController
public class OrderController {

	@RequestMapping("/api/order/addOrder")
	public String addOrder() {
		return "addOrder";
	}

	@RequestMapping("/orderInfo")
	public String orderInfo(){
		return "orderInfo";
	}
}

application.yml

server:
  port: 8081


logging:
  level:
    org.springframework.security: DEBUG

security:
  oauth2:
    resource:
      ####从认证授权中心上验证token
      tokenInfoUri: http://localhost:8080/oauth/check_token
      preferTokenInfo: true
    client:
      accessTokenUri: http://localhost:8080/oauth/token
      userAuthorizationUri: http://localhost:8080/oauth/authorize
      ###appid
      clientId: client_1
      ###appSecret
      clientSecret: 123456

启动类:

@SpringBootApplication
@EnableOAuth2Sso
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class, args);
    }
}

拦截器会拦截以/api/order开头的请求:
当我们访问 http://localhost:8081/orderInfo 连接时,不需要授权
在这里插入图片描述

访问需要授权的接口:http://localhost:8081/api/order/addOrder
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第10张图片
这时就要使用我们前面获取的accessToken进行访问,在请求头中添加如下参数就可以正常访问了:
Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第11张图片

注:正常情况下拦截服务资源 是在网关里面的!

拦截资源统一由网关进行Oauth2.0验证

开放接口和内部接口一定要独立出来! 可以封装业务逻辑相同,但是内部外部一定要相同

public开头的 需要做Oauth2.0验证的

动态读取表信息权限校验

官方推荐SQL:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);
 
 
create table oauth_client_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(25) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);
 
create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(250) 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 TIMESTAMP,
    lastModifiedAt TIMESTAMP
);
 
 
-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);

注:表名和字段名不要随便改。

在认证授权服务器中增加如下pom文件依赖:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jdbcartifactId>
dependency>

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
dependency>

增加配置文件信息:application.yml

spring:
  datasource:
    hikari:
      connection-test-query: SELECT 1
      minimum-idle: 1
      maximum-pool-size: 5
      pool-name: dbcp1
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/oauth2?autoReconnect=true&useSSL=false
    username: root
    password: 123456

配置认证授权:AuthorizationServerConfigByDataSource

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigByDataSource extends AuthorizationServerConfigurerAdapter {
    @Autowired
    @Qualifier("authenticationManager")
    private AuthenticationManager authenticationManager;

    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;

    // @Autowired
    // private UserDetailsService userDetailsService;

    @Bean
    public TokenStore tokenStore() {
        // return new InMemoryTokenStore(); //使用内存中的 token store
        return new JdbcTokenStore(dataSource); /// 使用Jdbctoken store
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        // 添加授权用户
        clients.jdbc(dataSource);
//		.withClient("client_1").secret(new BCryptPasswordEncoder().encode("123456"))
//		.authorizedGrantTypes("password", "refresh_token", "authorization_code")// 允许授权范围
//		.redirectUris("http://www.baidu.com").authorities("ROLE_ADMIN", "ROLE_USER")// 客户端可以使用的权限
//		.scopes("all").accessTokenValiditySeconds(7200).refreshTokenValiditySeconds(7200);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService());// 必须设置
        // UserDetailsService
        // 否则刷新token 时会报错
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();// 允许表单登录

    }

    @Bean
    AuthenticationManager authenticationManager() {
        AuthenticationManager authenticationManager = new AuthenticationManager() {

            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                return daoAuhthenticationProvider().authenticate(authentication);
            }
        };
        return authenticationManager;
    }

    @Bean
    public AuthenticationProvider daoAuhthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService());
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    // 设置添加用户信息,正常应该从数据库中读取
    @Bean
    UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
                .authorities("ROLE_USER").build());
        userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
                .authorities("ROLE_USER").build());
        return userDetailsService;
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        // 加密方式
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder;
    }
}

Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)_第12张图片
在这里插入图片描述
authentication_id 是加密的密码!

你可能感兴趣的:(springcloud,oauth2.0)