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.0</modelVersion>

  <groupId>com.donwait</groupId>

  <artifactId>DYS</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>pom</packaging>

  <name>道鹰4.0</name>

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

 

  <!-- spring boot项目 -->

  <parent>

      <groupId>org.springframework.boot</groupId>

      <artifactId>spring-boot-starter-parent</artifactId>

      <version>2.0.4.RELEASE</version>

      <relativePath/> <!-- lookup parent from repository -->

  </parent>

 

  <!-- 项目属性 -->

  <properties>

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

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

      <java.version>1.8</java.version>

      <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>

      <!-- <spring-cloud.version>Finchley.BUILD-SNAPSHOT</spring-cloud.version>-->

      <lombok.version>1.16.20</lombok.version>

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

  </properties>

 

  <!-- 项目依赖管理声明,统一管理项目依赖的版本信息,继承项目无需声明版本 -->

  <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>

          <dependency>

              <groupId>org.springframework</groupId>

              <artifactId>spring-jdbc</artifactId>

              <version>${spring.version}</version>

          </dependency>

      </dependencies>

  </dependencyManagement>

 

  <!-- 远程仓库:中央仓库找不到时候,从远程仓库中查找 -->

  <repositories>

      <repository>

          <id>spring-snapshots</id>

          <name>Spring Snapshots</name>

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

          <snapshots>

              <enabled>true</enabled>

          </snapshots>

      </repository>

      <repository>

          <id>spring-milestones</id>

          <name>Spring Milestones</name>

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

          <snapshots>

              <enabled>false</enabled>

          </snapshots>

      </repository>

  </repositories>

 

  <!-- 项目依赖 -->

  <dependencies>

     <!--spring boot测试-->

     <dependency>

          <groupId>org.springframework.boot</groupId>

          <artifactId>spring-boot-starter-test</artifactId>

          <scope>test</scope>

     </dependency>

     <!-- 监控系统健康情况的工具 -->

     <dependency>

          <groupId>org.springframework.boot</groupId>

          <artifactId>spring-boot-starter-actuator</artifactId>

     </dependency>

     <!-- 修改自动检测加载工具 -->

    <dependency>

          <groupId>org.springframework.boot</groupId>

          <artifactId>spring-boot-devtools</artifactId>

          <optional>true</optional>

     </dependency>

     <!--Lombok:消除模板代码-->

     <dependency>

          <groupId>org.projectlombok</groupId>

          <artifactId>lombok</artifactId>

     </dependency>

  </dependencies>

 

  <!-- 编译插件 -->

  <build>

      <plugins>

            <!-- SpringBoot编译插件 -->

          <plugin>

              <groupId>org.springframework.boot</groupId>

              <artifactId>spring-boot-maven-plugin</artifactId>

          </plugin>

          <!-- docker编译插件docker:build -->

          <!-- mvn clean package docker:build、mvn clean package docker:build -DpushImage -->

          <plugin>

                <groupId>com.spotify</groupId>

                <artifactId>docker-maven-plugin</artifactId>

                <version>0.4.13</version>

                <configuration>

                   <!-- push到docker仓库 -->

                   <!-- <imageName>${docker.registry}/${project.name}:${project.version}</imageName>-->

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

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

                     <skipDockerBuild>false</skipDockerBuild>

                   <!-- <pushImage>true</pushImage> -->

                   <!-- 这里是复制jar包到 docker容器指定目录配置,也可以写到 Docokerfile中 -->

                   <resources>

                        <resource>

                             <targetPath>/ROOT</targetPath>

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

                             <include>${project.build.finalName}.jar</include>

                        </resource>

                   </resources>

                </configuration>

            </plugin>

      </plugins>

  </build>

 

  <!-- 项目包含子模块 -->

  <modules>

     <!-- 公共组件 -->

     <module>dys_common</module>

     <!-- 链路跟踪服务 -->

     <module>dys_zipkin_server</module>

     <!-- 全局配置中心 -->

     <module>dys_config_center</module>

     <!-- 服务注册中心 -->

     <module>dys_register_server</module>

     <!-- 配置服务:提供外部访问接口 -->

     <module>dys_config_server</module>

     <!-- 网关服务器 -->

     <module>dys_gateway_server</module>

     <!-- 认证中心 -->

     <module>dys_auth_center</module>

     <module>dys_car_server</module>

  </modules>

 

</project>

 

一、认证服务器

 

(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.0</modelVersion>

  <artifactId>dys_auth_center</artifactId>

  <name>授权中心</name>

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

  

  <parent>

    <groupId>com.donwait</groupId>

    <artifactId>DYS</artifactId>

    <version>0.0.1-SNAPSHOT</version>

  </parent>

  

  <dependencies>

     <!-- 公共信息包 -->

     <dependency>

          <groupId>com.donwait</groupId>

          <artifactId>dys_common</artifactId>

          <version>0.0.1-SNAPSHOT</version>

     </dependency>

     <!-- 中心配置client -->

     <dependency>

         <groupId>org.springframework.cloud</groupId>

          <artifactId>spring-cloud-starter-config</artifactId>

     </dependency>

     <!-- spring cloud 客户注册 -->

     <dependency>

          <groupId>org.springframework.cloud</groupId>

          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

     </dependency>

     <!-- oauth2认证 -->

     <dependency>

          <groupId>org.springframework.cloud</groupId>

          <artifactId>spring-cloud-starter-oauth2</artifactId>

     </dependency>

     <!-- 安全校验 -->

     <dependency>

          <groupId>org.springframework.cloud</groupId>

          <artifactId>spring-cloud-starter-security</artifactId>

     </dependency>

     <!-- redis -->

     <dependency>

          <groupId>org.springframework.boot</groupId>

          <artifactId>spring-boot-starter-data-redis</artifactId>

     </dependency>

     <!-- MySQL -->

     <dependency>

          <groupId>mysql</groupId>

          <artifactId>mysql-connector-java</artifactId>

     </dependency>

     <!-- Spring-Mybatis -->

     <dependency>

          <groupId>org.mybatis.spring.boot</groupId>

          <artifactId>mybatis-spring-boot-starter</artifactId>

          <version>1.3.0</version>

     </dependency>

    <!-- 阿里巴巴druid数据库连接池 -->

     <dependency>

          <groupId>com.alibaba</groupId>

          <artifactId>druid-spring-boot-starter</artifactId>

          <version>1.1.9</version>

     </dependency>

     <dependency>

       <groupId>com.alibaba</groupId>

       <artifactId>druid</artifactId>

       <version>1.1.10</version>

     </dependency>

     <!-- RESTFul接口文档:通过接口访问文档:http://localhost:8762/swagger-ui.html,8762为服务配置的监听端口,默认8080-->

     <dependency>

          <groupId>io.springfox</groupId>

          <artifactId>springfox-swagger2</artifactId>

          <version>2.2.2</version>

     </dependency>

     <dependency>

          <groupId>io.springfox</groupId>

          <artifactId>springfox-swagger-ui</artifactId>

          <version>2.2.2</version>

     </dependency>

  </dependencies>

  

</project>

 

(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:lixiang6153@126.com

  hostory:

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

    user:lixiang6153@126.com

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

    user:test@126.com

  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<Object> 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<Object> 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<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {

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

        List<byte[]> byteList = null;

        RedisConnection conn = getConnection();

        try {

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

        } finally {

            conn.close();

        }

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

            return Collections.<OAuth2AccessToken> emptySet();

        }

        List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());

        for (byte[] bytes : byteList) {

            OAuth2AccessToken accessToken = deserializeAccessToken(bytes);

            accessTokens.add(accessToken);

        }

        return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);

    }

    @Override

    public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {

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

        List<byte[]> byteList = null;

        RedisConnection conn = getConnection();

        try {

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

        } finally {

            conn.close();

        }

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

            return Collections.<OAuth2AccessToken> emptySet();

        }

        List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());

        for (byte[] bytes : byteList) {

            OAuth2AccessToken accessToken = deserializeAccessToken(bytes);

            accessTokens.add(accessToken);

        }

        return Collections.<OAuth2AccessToken> 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<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();

        List<Right> 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.0</modelVersion>

  <artifactId>dys_car_server</artifactId>

  <name>车辆服务</name>

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

 

  <parent>

    <groupId>com.donwait</groupId>

    <artifactId>DYS</artifactId>

    <version>0.0.1-SNAPSHOT</version>

  </parent>

 

  <dependencies>

     <!-- 公共信息包 -->

     <dependency>

          <groupId>com.donwait</groupId>

          <artifactId>dys_common</artifactId>

          <version>0.0.1-SNAPSHOT</version>

     </dependency>

     <!-- SpringMVC -->

     <dependency>

          <groupId>org.springframework.boot</groupId>

          <artifactId>spring-boot-starter-web</artifactId>

     </dependency>

     <!-- 配置客户端 -->

     <dependency>

         <groupId>org.springframework.cloud</groupId>

         <artifactId>spring-cloud-starter-config</artifactId>

     </dependency>

     <!-- spring cloud 客户注册 -->

     <dependency>

          <groupId>org.springframework.cloud</groupId>

          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

     </dependency>

     <!-- MySQL -->

     <dependency>

          <groupId>mysql</groupId>

          <artifactId>mysql-connector-java</artifactId>

     </dependency>

     <!-- Spring-Mybatis -->

     <dependency>

          <groupId>org.mybatis.spring.boot</groupId>

          <artifactId>mybatis-spring-boot-starter</artifactId>

          <version>1.3.0</version>

     </dependency> 

    <!-- 阿里巴巴druid数据库连接池 -->

     <dependency>

          <groupId>com.alibaba</groupId>

          <artifactId>druid-spring-boot-starter</artifactId>

          <version>1.1.9</version>

     </dependency>

     <dependency>

       <groupId>com.alibaba</groupId>

       <artifactId>druid</artifactId>

       <version>1.1.10</version>

     </dependency>

     <!-- RESTFul接口文档:通过接口访问文档:http://localhost:8762/swagger-ui.html,8762为服务配置的监听端口,默认8080-->

     <dependency>

          <groupId>io.springfox</groupId>

          <artifactId>springfox-swagger2</artifactId>

          <version>2.2.2</version>

     </dependency>

     <dependency>

          <groupId>io.springfox</groupId>

          <artifactId>springfox-swagger-ui</artifactId>

          <version>2.2.2</version>

     </dependency>

     <!-- 消息中间件RabbitMQ -->

     <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-amqp</artifactId>

     </dependency>

     <!-- protobuf -->

     <dependency>

     <groupId>com.google.protobuf</groupId>

     <artifactId>protobuf-java</artifactId>

     <version>3.5.1</version>

    </dependency>

    <!-- 资源服务器 -->

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-security</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.security.oauth.boot</groupId>

        <artifactId>spring-security-oauth2-autoconfigure</artifactId>

    </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

 

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

 

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

 

 

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

 

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

 

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

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

 

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

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

 

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

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, 并且头部不使用授权方式:

 

 

快来成为我的朋友或合作伙伴,一起交流,一起进步!
QQ群:961179337
微信:lixiang6153
邮箱:lixx2048@163.com
公众号:IT技术快餐
更多资料等你来拿!

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝壳里的沙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值