29、oauth2.0 资源服务器与授权服务器分离配置

废话刷说,这个问题已经困扰我多时,我已经经过n次尝试,n-1都是失败的,最后只有一次成功,失败是成功他妈,是血的教训,佛说千万次错过就是为了那一次邂逅。

先把我遇到的问题罗列出来:

(1)资源服务器和认证服务器分离后,配置resource.id无效;

(2)存储的secret和用户密码都必须BCEncrypt加密;

(3)认证服务器同时也是资源服务器的时候,四种授权模式不能共生(我出现的是添加上资源服务器,授权码模式无效,implicit简单模式,password模式和client credentials模式有效;认证服务器不做为资源服务器的时候四种授权模式都可以生效)。

(4)Eureka注册服务,如果配置使用了Security,并配置HttpBasic登录模式后,可能与资源服务器的授权模式会冲突(最终注册服务器使用无需用户名和密码模式)。

 

父工程POM文件:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">

 

  <modelVersion>4.0.0modelVersion>

  <groupId>com.donwaitgroupId>

  <artifactId>DYSartifactId>

  <version>0.0.1-SNAPSHOTversion>

  <packaging>pompackaging>

  <name>道鹰4.0name>

  <description>道鹰4.0后台集群服务description>

 

 

  <parent>

      <groupId>org.springframework.bootgroupId>

      <artifactId>spring-boot-starter-parentartifactId>

      <version>2.0.4.RELEASEversion>

      <relativePath/>

  parent>

 

 

  <properties>

      <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>

      <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>

      <java.version>1.8java.version>

      <spring-cloud.version>Finchley.RELEASEspring-cloud.version>

     

      <lombok.version>1.16.20lombok.version>

      <docker.registry>localhost:5000docker.registry>

  properties>

 

 

  <dependencyManagement>

      <dependencies>

          <dependency>

              <groupId>org.springframework.cloudgroupId>

              <artifactId>spring-cloud-dependenciesartifactId>

              <version>${spring-cloud.version}version>

              <type>pomtype>

              <scope>importscope>

          dependency>

          <dependency>

              <groupId>org.springframeworkgroupId>

              <artifactId>spring-jdbcartifactId>

              <version>${spring.version}version>

          dependency>

      dependencies>

  dependencyManagement>

 

 

  <repositories>

      <repository>

          <id>spring-snapshotsid>

          <name>Spring Snapshotsname>

          <url>https://repo.spring.io/snapshoturl>

          <snapshots>

              <enabled>trueenabled>

          snapshots>

      repository>

      <repository>

          <id>spring-milestonesid>

          <name>Spring Milestonesname>

          <url>https://repo.spring.io/milestoneurl>

          <snapshots>

              <enabled>falseenabled>

          snapshots>

      repository>

  repositories>

 

 

  <dependencies>

     

     <dependency>

          <groupId>org.springframework.bootgroupId>

          <artifactId>spring-boot-starter-testartifactId>

          <scope>testscope>

     dependency>

     

     <dependency>

          <groupId>org.springframework.bootgroupId>

          <artifactId>spring-boot-starter-actuatorartifactId>

     dependency>

     

    <dependency>

          <groupId>org.springframework.bootgroupId>

          <artifactId>spring-boot-devtoolsartifactId>

          <optional>trueoptional>

     dependency>

     

     <dependency>

          <groupId>org.projectlombokgroupId>

          <artifactId>lombokartifactId>

     dependency>

  dependencies>

 

 

  <build>

      <plugins>

           

          <plugin>

              <groupId>org.springframework.bootgroupId>

              <artifactId>spring-boot-maven-pluginartifactId>

          plugin>

         

         

          <plugin>

                <groupId>com.spotifygroupId>

                <artifactId>docker-maven-pluginartifactId>

                <version>0.4.13version>

                <configuration>

                   

                   

                     <imageName>${project.name}:${project.version}imageName>

                     <dockerDirectory>${project.basedir}/src/main/dockerdockerDirectory>

                     <skipDockerBuild>falseskipDockerBuild>

                   

                   

                   <resources>

                        <resource>

                             <targetPath>/ROOTtargetPath>

                             <directory>${project.build.directory}directory>

                             <include>${project.build.finalName}.jarinclude>

                        resource>

                   resources>

                configuration>

            plugin>

      plugins>

  build>

 

 

  <modules>

     

     <module>dys_commonmodule>

     

     <module>dys_zipkin_servermodule>

     

     <module>dys_config_centermodule>

     

     <module>dys_register_servermodule>

     

     <module>dys_config_servermodule>

     

     <module>dys_gateway_servermodule>

     

     <module>dys_auth_centermodule>

     <module>dys_car_servermodule>

  modules>

 

project>

 

一、认证服务器

 

(1)POM配置文件配置

 

  4.0.0

  dys_auth_center

  授权中心

  提供用户认证以及平台服务器之间的token检查认证

  

  

    com.donwait

    DYS

    0.0.1-SNAPSHOT

  

  

  

     

     

          com.donwait

          dys_common

          0.0.1-SNAPSHOT

     

     

     

         org.springframework.cloud

          spring-cloud-starter-config

     

     

     

          org.springframework.cloud

          spring-cloud-starter-netflix-eureka-client

     

     

     

          org.springframework.cloud

          spring-cloud-starter-oauth2

     

     

     

          org.springframework.cloud

          spring-cloud-starter-security

     

     

     

          org.springframework.boot

          spring-boot-starter-data-redis

     

     

     

          mysql

          mysql-connector-java

     

     

     

          org.mybatis.spring.boot

          mybatis-spring-boot-starter

          1.3.0

     

    

     

          com.alibaba

          druid-spring-boot-starter

          1.1.9

     

     

       com.alibaba

       druid

       1.1.10

     

     

     

          io.springfox

          springfox-swagger2

          2.2.2

     

     

          io.springfox

          springfox-swagger-ui

          2.2.2

     

  

  

 

(2)认证服务器配置

package com.donwait.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.context.annotation.Primary;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.security.authentication.AuthenticationManager;

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.ClientDetailsService;

import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;

import org.springframework.security.oauth2.provider.token.DefaultTokenServices;



import com.donwait.redis.MyRedisTokenStore;

import com.donwait.service.impl.UserDetailsServiceImpl;



@Configuration

@EnableAuthorizationServer

public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {



    @Autowired

    private AuthenticationManager authenticationManager;



    @Autowired

    private DataSource dataSource;

    @Autowired

    private UserDetailsServiceImpl userDetailsService;



    @Autowired

    private RedisConnectionFactory redisConnectionFactory;



    @Bean

    @Primary

    MyRedisTokenStore redisTokenStore(){

        return new MyRedisTokenStore(redisConnectionFactory);

    }



    @Override

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.withClientDetails(clientDetails());

    }

    @Bean

    public ClientDetailsService clientDetails() {

        return new JdbcClientDetailsService(dataSource);

    }

    @Override

    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.tokenStore(redisTokenStore())

                .userDetailsService(userDetailsService)

                .authenticationManager(authenticationManager);

        endpoints.tokenServices(defaultTokenServices());

    }



    @Primary

    @Bean

    public DefaultTokenServices defaultTokenServices(){

        DefaultTokenServices tokenServices = new DefaultTokenServices();

        tokenServices.setTokenStore(redisTokenStore());

        tokenServices.setSupportRefreshToken(true);

        tokenServices.setClientDetailsService(clientDetails());

        tokenServices.setAccessTokenValiditySeconds(60*60*12);             // token有效期自定义设置,默认12小时

        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);    //默认30天,这里修改

        return tokenServices;

    }



    @Override

    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()");

        security .checkTokenAccess("isAuthenticated()");

        security.allowFormAuthenticationForClients();

    }

}

(3)Security资源保护配置

package com.donwait.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.builders.WebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;



import com.donwait.service.impl.UserDetailsServiceImpl;



@Configuration

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired

    private UserDetailsServiceImpl userDetailsService;

            

    @Bean

    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

    }



    @Override

    @Bean

    public AuthenticationManager authenticationManagerBean() throws Exception {

        return super.authenticationManagerBean();

    }

    @Override

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService)

            .passwordEncoder(passwordEncoder());

    }



    @Override

    public void configure(WebSecurity web) throws Exception {

        web.ignoring().antMatchers("/favor.ioc");

    }



}

(4)配置文件配置

server:

  port: 7006

spring:

  application:

    name: auth

  datasource:

    name: store_service

    type: com.alibaba.druid.pool.DruidDataSource

    druid:

      driver-class-name: com.mysql.jdbc.Driver

      url: jdbc:mysql://127.0.0.1:3306/dys_auth_center?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false

      username: root

      password: root

      initial-size: 5

      min-idle: 5

      max-active: 20

      max-wait: 60000

      time-between-eviction-runs-millis: 60000

      min-evictable-idle-time-millis: 300000

      validation-query: SELECT 'x'

      validation-query-timeout: 6

      test-while-idle: true

      test-on-borrow: false

      test-on-return: false

      pool-prepared-statements: false

      max-pool-prepared-statement-per-connection-size: 20

      filters: stat

      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

mybatis:

  mapper-locations: classpath:mapping/*.xml

  type-aliases-package: com.donwait.model

 

eureka:

  instance:

    prefer-ip-address: true

    registry-fetch-interval-seconds: 30

    lease-renewal-interval-in-seconds: 10

    lease-expiration-duration-in-seconds: 30

  client:

      service-url:

        defaultZone: http://127.0.0.1:7002/eureka/,http://127.0.0.1:7003/eureka/

endpoints:

  health:

    sensitive: false

    enabled: false

  shutdown:

    enabled: true

    path: /shutdown

    sensitive: true

management:

  security:

    enabled: false

security:

  oauth2:

    resource:

      filter-order: 3

logging:

  config: classpath:logback.xml

  level:

    org:

      springframework:

        web: INFO

        security: DEBUG

#actuator:

info:

  author:

    name: 李祥祥

    email:[email protected]

  hostory:

  - date: 2018-08-28 10:10:10

    user:[email protected]

  - date: 2018-07-10 08:30:00

    user:[email protected]

  build:

    artifact: "@project.artifactId@"

    name: "@project.name@"

    version: "@project.version@"

 

(5)启动入口

package com.donwait;



import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;



/**

* http://localhost:7006/oauth/authorize?response_type=code&client_id=wx_takeout_client_id&redirect_uri=http://localhost:7010/uaa/login

* @author Administrator

*/

@SpringBootApplication                    // SpringBoot应用

@EnableDiscoveryClient                    // 开启服务发现功能

@MapperScan("com.donwait.mapper")        // 将项目中对应的mapper类的路径加进来就可以了

public class AuthServerApp {



    public static void main(String[] args) {;

        SpringApplication.run(AuthServerApp.class, args);

    }

}

 

(6)redis配置

package com.donwait.redis;



import java.util.ArrayList;

import java.util.Collection;

import java.util.Collections;

import java.util.Date;

import java.util.List;



import org.springframework.data.redis.connection.RedisConnection;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;

import org.springframework.security.oauth2.common.OAuth2AccessToken;

import org.springframework.security.oauth2.common.OAuth2RefreshToken;

import org.springframework.security.oauth2.provider.OAuth2Authentication;

import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;

import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;

import org.springframework.security.oauth2.provider.token.TokenStore;

import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;

import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;

import org.springframework.stereotype.Component;



import lombok.Data;



@Data

@Component

public class MyRedisTokenStore implements TokenStore {

    private static final String ACCESS = "access:";

    private static final String AUTH_TO_ACCESS = "auth_to_access:";

    private static final String AUTH = "auth:";

    private static final String REFRESH_AUTH = "refresh_auth:";

    private static final String ACCESS_TO_REFRESH = "access_to_refresh:";

    private static final String REFRESH = "refresh:";

    private static final String REFRESH_TO_ACCESS = "refresh_to_access:";

    private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";

    private static final String UNAME_TO_ACCESS = "uname_to_access:";

    private final RedisConnectionFactory connectionFactory;

    private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();

    private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();

    private String prefix = "";



    public MyRedisTokenStore(RedisConnectionFactory connectionFactory) {

        this.connectionFactory = connectionFactory;

    }

    

    private byte[] serialize(Object object) {

        return this.serializationStrategy.serialize(object);

    }

    

    private byte[] serializeKey(String object) {

        return this.serialize(this.prefix + object);

    }



    private OAuth2AccessToken deserializeAccessToken(byte[] bytes) {

        return (OAuth2AccessToken)this.serializationStrategy.deserialize(bytes, OAuth2AccessToken.class);

    }

    private OAuth2Authentication deserializeAuthentication(byte[] bytes) {

        return (OAuth2Authentication)this.serializationStrategy.deserialize(bytes, OAuth2Authentication.class);

    }

    private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) {

        return (OAuth2RefreshToken)this.serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class);

    }

    

    private String deserializeString(byte[] bytes) {

        return this.serializationStrategy.deserializeString(bytes);

    }

    

    private static String getApprovalKey(OAuth2Authentication authentication) {

        String userName = authentication.getUserAuthentication() == null ? "": authentication.getUserAuthentication().getName();

        return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);

    }

    private static String getApprovalKey(String clientId, String userName) {

        return clientId + (userName == null ? "" : ":" + userName);

    }



    private RedisConnection getConnection() {

        return this.connectionFactory.getConnection();

    }

    

    @Override

    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {

        String key = this.authenticationKeyGenerator.extractKey(authentication);

        byte[] serializedKey = this.serializeKey(AUTH_TO_ACCESS + key);

        byte[] bytes = null;

        RedisConnection conn = this.getConnection();

        try {

            bytes = conn.get(serializedKey);

        } finally {

            conn.close();

        }

        OAuth2AccessToken accessToken = this.deserializeAccessToken(bytes);

        if (accessToken != null) {

            OAuth2Authentication storedAuthentication = this.readAuthentication(accessToken.getValue());

            if (storedAuthentication == null || !key.equals(this.authenticationKeyGenerator.extractKey(storedAuthentication))) {

                this.storeAccessToken(accessToken, authentication);

            }

        }

        return accessToken;

    }

    @Override

    public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {

        return this.readAuthentication(token.getValue());

    }

    @Override

    public OAuth2Authentication readAuthentication(String token) {

        byte[] bytes = null;

        RedisConnection conn = this.getConnection();

        try {

            bytes = conn.get(this.serializeKey("auth:" + token));

        } finally {

            conn.close();

        }

        OAuth2Authentication auth = this.deserializeAuthentication(bytes);

        return auth;

    }

    @Override

    public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {

        return this.readAuthenticationForRefreshToken(token.getValue());

    }

    public OAuth2Authentication readAuthenticationForRefreshToken(String token) {

        RedisConnection conn = getConnection();

        try {

            byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));

            OAuth2Authentication auth = deserializeAuthentication(bytes);

            return auth;

        } finally {

            conn.close();

        }

    }

    @Override

    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {

        byte[] serializedAccessToken = serialize(token);

        byte[] serializedAuth = serialize(authentication);

        byte[] accessKey = serializeKey(ACCESS + token.getValue());

        byte[] authKey = serializeKey(AUTH + token.getValue());

        byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));

        byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));

        byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());

        RedisConnection conn = getConnection();

        try {

            conn.openPipeline();

            conn.stringCommands().set(accessKey, serializedAccessToken);

            conn.stringCommands().set(authKey, serializedAuth);

            conn.stringCommands().set(authToAccessKey, serializedAccessToken);

            if (!authentication.isClientOnly()) {

                conn.rPush(approvalKey, serializedAccessToken);

            }

            conn.rPush(clientId, serializedAccessToken);

            if (token.getExpiration() != null) {

                int seconds = token.getExpiresIn();

                conn.expire(accessKey, seconds);

                conn.expire(authKey, seconds);

                conn.expire(authToAccessKey, seconds);

                conn.expire(clientId, seconds);

                conn.expire(approvalKey, seconds);

            }

            OAuth2RefreshToken refreshToken = token.getRefreshToken();

            if (refreshToken != null && refreshToken.getValue() != null) {

                byte[] refresh = serialize(token.getRefreshToken().getValue());

                byte[] auth = serialize(token.getValue());

                byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());

                conn.stringCommands().set(refreshToAccessKey, auth);

                byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue());

                conn.stringCommands().set(accessToRefreshKey, refresh);

                if (refreshToken instanceof ExpiringOAuth2RefreshToken) {

                    ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;

                    Date expiration = expiringRefreshToken.getExpiration();

                    if (expiration != null) {

                        int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)

                                .intValue();

                        conn.expire(refreshToAccessKey, seconds);

                        conn.expire(accessToRefreshKey, seconds);

                    }

                }

            }

            conn.closePipeline();

        } finally {

            conn.close();

        }

    }

    @Override

    public void removeAccessToken(OAuth2AccessToken accessToken) {

        this.removeAccessToken(accessToken.getValue());

    }

    @Override

    public OAuth2AccessToken readAccessToken(String tokenValue) {

        byte[] key = serializeKey(ACCESS + tokenValue);

        byte[] bytes = null;

        RedisConnection conn = getConnection();

        try {

            bytes = conn.get(key);

        } finally {

            conn.close();

        }

        OAuth2AccessToken accessToken = deserializeAccessToken(bytes);

        return accessToken;

    }

    public void removeAccessToken(String tokenValue) {

        byte[] accessKey = serializeKey(ACCESS + tokenValue);

        byte[] authKey = serializeKey(AUTH + tokenValue);

        byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);

        RedisConnection conn = getConnection();

        try {

            conn.openPipeline();

            conn.get(accessKey);

            conn.get(authKey);

            conn.del(accessKey);

            conn.del(accessToRefreshKey);

            // Don't remove the refresh token - it's up to the caller to do that

            conn.del(authKey);

            List results = conn.closePipeline();

            byte[] access = (byte[]) results.get(0);

            byte[] auth = (byte[]) results.get(1);

            OAuth2Authentication authentication = deserializeAuthentication(auth);

            if (authentication != null) {

                String key = authenticationKeyGenerator.extractKey(authentication);

                byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key);

                byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));

                byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());

                conn.openPipeline();

                conn.del(authToAccessKey);

                conn.lRem(unameKey, 1, access);

                conn.lRem(clientId, 1, access);

                conn.del(serialize(ACCESS + key));

                conn.closePipeline();

            }

        } finally {

            conn.close();

        }

    }

    @Override

    public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {

        byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue());

        byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue());

        byte[] serializedRefreshToken = serialize(refreshToken);

        RedisConnection conn = getConnection();

        try {

            conn.openPipeline();

            conn.stringCommands().set(refreshKey, serializedRefreshToken);

            conn.stringCommands().set(refreshAuthKey, serialize(authentication));

            if (refreshToken instanceof ExpiringOAuth2RefreshToken) {

                ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;

                Date expiration = expiringRefreshToken.getExpiration();

                if (expiration != null) {

                    int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)

                            .intValue();

                    conn.expire(refreshKey, seconds);

                    conn.expire(refreshAuthKey, seconds);

                }

            }

            conn.closePipeline();

        } finally {

            conn.close();

        }

    }

    @Override

    public OAuth2RefreshToken readRefreshToken(String tokenValue) {

        byte[] key = serializeKey(REFRESH + tokenValue);

        byte[] bytes = null;

        RedisConnection conn = getConnection();

        try {

            bytes = conn.get(key);

        } finally {

            conn.close();

        }

        OAuth2RefreshToken refreshToken = deserializeRefreshToken(bytes);

        return refreshToken;

    }

    @Override

    public void removeRefreshToken(OAuth2RefreshToken refreshToken) {

        this.removeRefreshToken(refreshToken.getValue());

    }

    public void removeRefreshToken(String tokenValue) {

        byte[] refreshKey = serializeKey(REFRESH + tokenValue);

        byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue);

        byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue);

        byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);

        RedisConnection conn = getConnection();

        try {

            conn.openPipeline();

            conn.del(refreshKey);

            conn.del(refreshAuthKey);

            conn.del(refresh2AccessKey);

            conn.del(access2RefreshKey);

            conn.closePipeline();

        } finally {

            conn.close();

        }

    }

    @Override

    public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {

        this.removeAccessTokenUsingRefreshToken(refreshToken.getValue());

    }

    private void removeAccessTokenUsingRefreshToken(String refreshToken) {

        byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken);

        List results = null;

        RedisConnection conn = getConnection();

        try {

            conn.openPipeline();

            conn.get(key);

            conn.del(key);

            results = conn.closePipeline();

        } finally {

            conn.close();

        }

        if (results == null) {

            return;

        }

        byte[] bytes = (byte[]) results.get(0);

        String accessToken = deserializeString(bytes);

        if (accessToken != null) {

            removeAccessToken(accessToken);

        }

    }

    public Collection findTokensByClientIdAndUserName(String clientId, String userName) {

        byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName));

        List byteList = null;

        RedisConnection conn = getConnection();

        try {

            byteList = conn.lRange(approvalKey, 0, -1);

        } finally {

            conn.close();

        }

        if (byteList == null || byteList.size() == 0) {

            return Collections. emptySet();

        }

        List accessTokens = new ArrayList(byteList.size());

        for (byte[] bytes : byteList) {

            OAuth2AccessToken accessToken = deserializeAccessToken(bytes);

            accessTokens.add(accessToken);

        }

        return Collections. unmodifiableCollection(accessTokens);

    }

    @Override

    public Collection findTokensByClientId(String clientId) {

        byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId);

        List byteList = null;

        RedisConnection conn = getConnection();

        try {

            byteList = conn.lRange(key, 0, -1);

        } finally {

            conn.close();

        }

        if (byteList == null || byteList.size() == 0) {

            return Collections. emptySet();

        }

        List accessTokens = new ArrayList(byteList.size());

        for (byte[] bytes : byteList) {

            OAuth2AccessToken accessToken = deserializeAccessToken(bytes);

            accessTokens.add(accessToken);

        }

        return Collections. unmodifiableCollection(accessTokens);

    }

}

(7)redis密码比对

package com.donwait.redis;



import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.factory.PasswordEncoderFactories;

import org.springframework.security.crypto.password.PasswordEncoder;



/**

* PasswordEncoder密码验证clientId的时候会报错,因为5.0新特性中需要在密码前方需要加上{Xxx}来判别。

* 所以需要自定义一个类,重新BCryptPasswordEncoder的match方法

* MyBCryptPasswordEncoder类是我自定义的一个类,用来重新match方法

* @author Administrator

*/

public class MyBCryptPasswordEncoder extends BCryptPasswordEncoder {

    @Override

    public boolean matches(CharSequence rawPassword, String encodedPassword) {

        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

        String presentedPassword = passwordEncoder.encode(rawPassword);

        return passwordEncoder.matches(rawPassword, presentedPassword);

    }

}

(8)UserDetailsService接口实现

package com.donwait.service.impl;



import java.util.HashSet;

import java.util.List;

import java.util.Set;



import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

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.stereotype.Service;



import com.donwait.model.Right;

import com.donwait.service.RightService;

import com.donwait.service.UserService;



@Service

public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired

    private UserService userService;                // 用户服务

    @Autowired

    private RightService rightService;                // 权限服务



    @Override

    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

        // 查找用户

        com.donwait.model.User user = userService.findByUsername(userName);

        if (null == user) {

            throw new UsernameNotFoundException("用户:" + userName + ",不存在!");

        }

        

        // 设置用户权限

        Set grantedAuthorities = new HashSet();

        List rights = rightService.findByUsername(userName);

        for(Right right : rights) {

            GrantedAuthority authority = new SimpleGrantedAuthority(right.getName());

            grantedAuthorities.add(authority);

        }       

        

        // 标识位设置

        boolean enabled = true;                         // 可用性 :true:可用 false:不可用

        boolean accountNonExpired = true;                 // 过期性 :true:没过期 false:过期

        boolean credentialsNonExpired = true;             // 有效性 :true:凭证有效 false:凭证无效

        boolean accountNonLocked = true;                 // 锁定性 :true:未锁定 false:已锁定

        

        return new User(user.getUsername(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);

    }

}

(9)注销token配置

package com.donwait.endpoint;



import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpoint;

import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.ResponseBody;



@FrameworkEndpoint

public class RevokeTokenEndpoint {

    @Autowired

    private ConsumerTokenServices consumerTokenServices;



    @RequestMapping(value = "/oauth/token", method= RequestMethod.DELETE)

    public @ResponseBody

    String revokeToken(String access_token){

        String msg = "";

        if (consumerTokenServices.revokeToken(access_token)){

            msg = "注销成功";

        }else {

            msg = "注销失败";

        }

        return msg;

    }

}

二、资源服务器配置

 

(1)POM配置

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0modelVersion>

  <artifactId>dys_car_serverartifactId>

  <name>车辆服务name>

  <description>提供平台车辆管理、车牌报警等相关服务description>

 

  <parent>

    <groupId>com.donwaitgroupId>

    <artifactId>DYSartifactId>

    <version>0.0.1-SNAPSHOTversion>

  parent>

 

  <dependencies>

     

     <dependency>

          <groupId>com.donwaitgroupId>

          <artifactId>dys_commonartifactId>

          <version>0.0.1-SNAPSHOTversion>

     dependency>

     

     <dependency>

          <groupId>org.springframework.bootgroupId>

          <artifactId>spring-boot-starter-webartifactId>

     dependency>

     

     <dependency>

         <groupId>org.springframework.cloudgroupId>

         <artifactId>spring-cloud-starter-configartifactId>

     dependency>

     

     <dependency>

          <groupId>org.springframework.cloudgroupId>

          <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>

     dependency>

     

     <dependency>

          <groupId>mysqlgroupId>

          <artifactId>mysql-connector-javaartifactId>

     dependency>

     

     <dependency>

          <groupId>org.mybatis.spring.bootgroupId>

          <artifactId>mybatis-spring-boot-starterartifactId>

          <version>1.3.0version>

     dependency> 

   

     <dependency>

          <groupId>com.alibabagroupId>

          <artifactId>druid-spring-boot-starterartifactId>

          <version>1.1.9version>

     dependency>

     <dependency>

       <groupId>com.alibabagroupId>

       <artifactId>druidartifactId>

       <version>1.1.10version>

     dependency>

     

     <dependency>

          <groupId>io.springfoxgroupId>

          <artifactId>springfox-swagger2artifactId>

          <version>2.2.2version>

     dependency>

     <dependency>

          <groupId>io.springfoxgroupId>

          <artifactId>springfox-swagger-uiartifactId>

          <version>2.2.2version>

     dependency>

     

     <dependency>

        <groupId>org.springframework.bootgroupId>

        <artifactId>spring-boot-starter-amqpartifactId>

     dependency>

     

     <dependency>

     <groupId>com.google.protobufgroupId>

     <artifactId>protobuf-javaartifactId>

     <version>3.5.1version>

    dependency>

   

    <dependency>

        <groupId>org.springframework.cloudgroupId>

        <artifactId>spring-cloud-starter-securityartifactId>

    dependency>

    <dependency>

        <groupId>org.springframework.security.oauth.bootgroupId>

        <artifactId>spring-security-oauth2-autoconfigureartifactId>

    dependency>

  dependencies>

 

project>

 

(2)资源服务器配置

package com.donwait.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

public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    // 改资源服务器id必须在数据库记录中有配置,也就是对应token的用户必须该资源访问权限(密文:test_resource_secret)

    // 例如,我的数据库记录:'wx_takeout_client_id','test_resource_id','$2a$10$I28j9B0T/roapkMEqfIHguARt0GgLyXwC/DOnFwPpXuQ0xTkrd632','user_info','authorization_code,refresh_token,implicit,password','http://localhost:7010/uaa/login','ROLE_ADMIN,ROLE_DEVICE,ROLE_VIDEO',3600,7200,'{\"systemInfo\":\"Atlas System\"}','true'

    // 通过授权模式或简化模式获取的token(对应用户为wx_takeout_client_id)具有访问资源服务器test_resource_id的权限,所以将改资源服务器id要与数据库的对应,否则无法访问

    private static final String DEMO_RESOURCE_ID = "test_resource_id";

    

    /**

     * 以代码形式配置资源服务器id,配置文件配置不生效

     */

    @Override

    public void configure(ResourceServerSecurityConfigurer resources) {

        resources.resourceId(DEMO_RESOURCE_ID).stateless(true);

    }

    

    @Override

    public void configure(HttpSecurity http) throws Exception {

        http

        .authorizeRequests()

        .antMatchers("/add/**")

        .authenticated();

    }

}

(3)控制器

package com.donwait.controller;



import org.springframework.security.core.Authentication;

import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;



import io.swagger.annotations.Api;

import io.swagger.annotations.ApiOperation;

import lombok.extern.slf4j.Slf4j;



@Slf4j

@Api(value = "车牌服务控制器", description = "提供车牌抓拍搜索等相关接口")

@RestController

public class CarAlarmController {

    

    @ApiOperation(value="增加车牌抓拍报警", notes="上传车牌识别报警信息并入库")

    @RequestMapping("/add/{id}")

    public String add(@PathVariable String id) {

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        log.debug("授权信息:{}",authentication);        

        return "Add car alarm";

    }

}

(4)程序入口

package com.donwait;



import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;



@SpringBootApplication

@EnableOAuth2Sso

public class CarApp {



    public static void main(String[] args) {

        SpringApplication.run(CarApp.class, args);

    }



}

三、使用流程

(1)使用授权码模式获取token

打开浏览器,输入:http://localhost:7006/oauth/authorize?response_type=code&client_id=wx_takeout_client_id&redirect_uri=http://localhost:7010/uaa/login

 

重定向到登录页面:输入用户名密码

 

29、oauth2.0 资源服务器与授权服务器分离配置_第1张图片

登录成功后重定向到redirect_uri并携带了code授权码

 

29、oauth2.0 资源服务器与授权服务器分离配置_第2张图片

 

使用postman,输入授权码和其他参数,换取token

29、oauth2.0 资源服务器与授权服务器分离配置_第3张图片

 

(2)通过从认证服务器换取的token访问资源服务器

 

使用GET方式,携带上token授权码,授权码放在header中:

29、oauth2.0 资源服务器与授权服务器分离配置_第4张图片

注意:这里必须选择bearer token,这是oauth默认的认证方式,发送后即可返回结果

29、oauth2.0 资源服务器与授权服务器分离配置_第5张图片

 

备注:当然获取token的过程可以使用四种授权模式的任意一种,比如可以使用password模式:

29、oauth2.0 资源服务器与授权服务器分离配置_第6张图片

此处的授权认证的username为client_id密码为client_secret, 登录的用户名是lixx,用户名lixx的密码为dw123456

29、oauth2.0 资源服务器与授权服务器分离配置_第7张图片

 

重点: 另外一种配置方式:

security: 

  oauth2:

    resource:

      filter-order: 3

      id: test_resource_id

      tokenInfoUri: http://localhost:7006/oauth/check_token

      preferTokenInfo: true

      

最后两行改为,不使用tokeninfo验证,使用用户信息验证:

      user-info-uri:http://localhost:7006/user/principal

        prefer-token-info: false

 

此时资源请求方式为: 直接在url后面带上access_token=xxx, 并且头部不使用授权方式:

 

29、oauth2.0 资源服务器与授权服务器分离配置_第8张图片

你可能感兴趣的:(spring,cloud)