实战-基础SpringCloud的搭建

一、建立一个总工程
首先建立一个名为clouddo的总工程
在这里插入图片描述
在这里插入图片描述

父工程的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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sxl</groupId>
  <artifactId>clouddo</artifactId>
  <version>0.0.1-SNAPSHOT</version>//版本号
  <packaging>pom</packaging>
  <properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<junit.version>4.12</junit.version>
		<log4j.version>1.2.17</log4j.version>
		<lombok.version>1.16.18</lombok.version>
	</properties>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.SR1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>1.5.9.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
			    <groupId>mysql</groupId>
			    <artifactId>mysql-connector-java</artifactId>
			    <version>5.1.46</version>
			</dependency>
			<dependency>
			    <groupId>com.alibaba</groupId>
			    <artifactId>druid</artifactId>
			    <version>1.1.0</version>
			</dependency>
			<dependency>
				<groupId>org.mybatis.spring.boot</groupId>
				<artifactId>mybatis-spring-boot-starter</artifactId>
				<version>1.3.0</version>
			</dependency>
			<dependency>
				<groupId>ch.qos.logback</groupId>
				<artifactId>logback-core</artifactId>
				<version>1.2.3</version>
			</dependency>
			<dependency>
				<groupId>junit</groupId>
				<artifactId>junit</artifactId>
				<version>${junit.version}</version>
				<scope>test</scope>
			</dependency>
			<dependency>
				<groupId>log4j</groupId>
				<artifactId>log4j</artifactId>
				<version>${log4j.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>
</project>

二、创建注册和服务中心子工程
然后新建一个clouddo-server注册和服务中心,用来注册和发现服务
在这里插入图片描述
在这里插入图片描述
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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.sxl</groupId>
    <artifactId>clouddo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>clouddo-server</artifactId>
  <dependencies>
		<!--eureka-server服务端 -->
		<dependency>
    		<groupId>org.springframework.cloud</groupId>
    		<artifactId>spring-cloud-netflix-eureka-server</artifactId>
		</dependency>
		<!-- 修改后立即生效,热部署 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>springloaded</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
	</dependencies>
</project>

配置他的application.yml文件如下:

server:
  port: 9001


eureka:
  instance:
  #主机名称
    hostname:clouddoServer
  client:
     # 是否向eureka注册自己
    register-with-eureka: false
       # 是否检索其他服务信息
    fetch-registry: false
      # 地址
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server: 
      enable-self-preservation: false # 开发模式关闭服务保护机制


创建这个服务的启动类:

package com.sxl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class ClouddoServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ClouddoServerApplication.class, args);
		System.out.println("ヾ(◍°∇°◍)ノ゙    注册中心启动成功      ヾ(◍°∇°◍)ノ゙\n");
	}
}

注意:
1. @EnableEurekaServer注解的作用是通过EnableEurekaServer注解启动一个服务注册中心
2. @SpringBootApplication注解的作用是用于快捷配置启动类,他是一个组合注解

现在可以启动注册中心了!启动成功后,显示如下:
在这里插入图片描述
现在还没有添加任何服务,所以服务是空的。

三、创建配置中心子工程
我们在开发大的系统时,由于服务较多,相同的配置(如数据库信息、缓存、开关量等)会出现在不同的服务上,如果一个配置发生变化,则可能需要修改很多的服务配置。为了解决这个问题,spring cloud提供配置中心。
首先所有的公共配置存储在相同的地址(存储的地方可以是git,svn和本地文件),然后配置中心从这些地方读取配置以restful发布出来,其它服务可以调用接口获取配置信息。
配置中心的建法和上面相同,这里就不详细创建了,下面的子工程的创建也一样,就不一一解释了。
pom文件配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

	<groupId>com.sxl</groupId>
    <artifactId>clouddo-config</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>

    <name>clouddo-config</name>
    <description>配置中心服务</description>

    <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.SR1</spring-cloud.version>
    </properties>


    <dependencies>
    	<!-- feign -->
	    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
<!--         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency> -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

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

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <!--暴露各种指标   -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

其application.yml配置如下:

server:
  port: 9002
  
spring:
  application:
    name: clouddo-config
  #代表本地文件
  profiles: 
    active: native
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config #切记一定不要忘记冒号  不然很尬
eureka:
  instance:
#    使用IP注册
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${spring.application.instance_id:${server.port}}
# 心跳检测检测与续约时间
# 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服务
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 10
  client:
    register-with-eureka: true
    fetch-registry: false
    service-url:
      defaultZone:  http://192.168.0.118:9001/eureka/


创建其启动类,代码如下:

package com.sxl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ClouddoConfigApplication {
	 public static void main(String[] args) {
	        SpringApplication.run(ClouddoConfigApplication.class, args);
	        System.out.println("ヾ(◍°∇°◍)ノ゙    配置中心启动成功      ヾ(◍°∇°◍)ノ゙\n");
	    }
}

完成后,就可以启动项目了

四、创建网关子工程
新建一个名为clouddo-zuul的子工程,建法同上。Zuul路由包含了对请求的路由和过滤两个功能,所以此子工程的作用主要是负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口。

pom配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.sxl</groupId>
	<artifactId>clouddo-zuul</artifactId>
	<version>0.0.1</version>
	<packaging>jar</packaging>

	<name>clouddo-zuul</name>
	<description>网关服务</description>

	<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.SR1</spring-cloud.version>
	</properties>


	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>

		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        
		<dependency>
		    <groupId>org.springframework.cloud</groupId>
		    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

同时将网关微服务注册到注册中心上,因为我们创建了一个配置中心,所以需要在配置中心添加一个配置,在子工程中注册到注册中心即可。配置如下:
在这里插入图片描述
配置中心的.yml中配置:

server:
  port: 9003
eureka:
  instance:
    #    使用IP注册
    lease-renewal-interval-in-seconds: 10 # 每间隔1s,向服务端发送一次心跳,证明自己依然”存活“
    lease-expiration-duration-in-seconds: 10 # 告诉服务端,如果我2s之内没有给你发心跳,就代表我“死”了,将我踢出掉。
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${spring.application.instance_id:${server.port}}
  client:
    service-url:
      defaultZone:  http://192.168.0.110:9001/eureka/
spring:
  servlet:
    multipart:
      max-file-size: 100Mb
      max-request-size: 100Mb
zuul:
  host:
      connections: 500
      connect-timeout-millis: 60000
      socket-timeout-millis: 60000
#----------------------超时配置-------------------
ribbon:
  ReadTimeout: 3500
  ConnectTimeout: 1000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0
  eureka:
  #是否开启超时熔断
    enabled: true
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 50000

子工程中bootstrap.yml的配置如下:

spring:
  application:
    name: cloud-zuul #服务名称
  cloud:
    config:
      uri: http://192.168.0.110:9002 #配置中心的地址
      enabled: true #开启配置
      profile: dev  #版本
      name: clouddo-zuul #配置中心的配置文件名称
      label: master  #分支名称

在配置这个的过程中,发现一个很坑人的事情,eclipse读取配置的时候是先去找bootstrap.yml文件,然后在去读取application.yml文件,我一开始配置的是application.yml文件,启动的时候一直报错,原因就是:他先读取bootstrap的时候没有找到,但是已经算读取完配置了,就不再读取application中的配置了,导致一直报错。所以这里创建的文档名字是bootstrap

添加其启动类代码如下:

package com.sxl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ZuulApplication {
	public static void main(String[] args) {
		SpringApplication.run(ZuulApplication.class, args);
		System.out.println("ヾ(◍°∇°◍)ノ゙   ZUUL网关启动成功      ヾ(◍°∇°◍)ノ゙\n");
	}
	
//	@Bean
//	public AccessFilter accessFilter(){
//		return new AccessFilter();
//	}

	@Bean
	public CorsFilter corsFilter() {
		final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		final CorsConfiguration config = new CorsConfiguration();
		config.setAllowCredentials(true); // 允许cookies跨域
		config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
		config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
		config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
		config.addAllowedMethod("OPTIONS");// 允许提交请求的方法,*表示全部允许
		config.addAllowedMethod("HEAD");
		config.addAllowedMethod("GET");// 允许Get的请求方法
		config.addAllowedMethod("PUT");
		config.addAllowedMethod("POST");
		config.addAllowedMethod("DELETE");
		config.addAllowedMethod("PATCH");
		source.registerCorsConfiguration("/**", config);
		return new CorsFilter(source);
	}
}

配置完成后就可以启动了,启动后注册中心应显示出启动的服务:
在这里插入图片描述
五、我们前面配置了一个认证中心用来存放所有的配置,为了方便起见,我们在创建一个公共类微服务,用来存放公共的类和方法。
创建名为clouddo-common的微服务
pom配置如下:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.sxl</groupId>
    <artifactId>clouddo-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
  
    <name>clouddo-common</name>
  	<description>公用工具类</description>
  
    <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.SR1</spring-cloud.version>
        <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
    </properties>
    
    
  <dependencies>
 	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>  
	<groupId>commons-beanutils</groupId>  
	<artifactId>commons-beanutils</artifactId>  
	<version>1.9.3</version>  
	</dependency>  
	<dependency>  
	<groupId>commons-collections</groupId>  
	<artifactId>commons-collections</artifactId>  
	<version>3.2.1</version>  
	</dependency>  
	<dependency>  
	<groupId>commons-lang</groupId>  
	<artifactId>commons-lang</artifactId>  
	<version>2.6</version>  
	</dependency>  
	<dependency>  
	<groupId>commons-logging</groupId>  
	<artifactId>commons-logging</artifactId>  
	<version>1.1.1</version>  
	</dependency>  
	<dependency>  
	<groupId>net.sf.ezmorph</groupId>  
	<artifactId>ezmorph</artifactId>  
	<version>1.0.6</version>  
	</dependency>  
	<dependency>  
	<groupId>net.sf.json-lib</groupId>  
	<artifactId>json-lib</artifactId>  
	<version>2.2.3</version>  
	<classifier>jdk15</classifier><!-- jdk版本 -->  
	</dependency>  
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  
      <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

创建com.sxl.config和com.sxl.dto的包包下分别创建ResponseCodeClass.java和BaseDto.java,作用分别是统一规定一些返回码和返回格式
代码如下:

package com.sxl.config;

/**
 * 常用返回码整理
 * 2017年10月12日15:33:32
 * @author lin
 *
 */
public enum ResponseCode {
	
	/**
	 * 执行成功
	 */
	SUCCESS(200,"执行成功"),
	
	/**
	 * 参数错误
	 */
	BAD_REQUEST(400, "参数错误"),
	
	/**
	 * 未登录
	 */
    UNAUTHORIZED(401, "未登录"),
    
    /**
     * 没有权限
     */
    FORBIDDEN(403, "没有权限"),
    
    /**
     * 操作失败
     */
    EXPECTATION_FAILED(417, "操作失败"),
    
    /**
     * 系统异常
     */
	SERVER_ERROR(500, "系统异常"),
	
	/**
	 * 用户名或密码错误
	 */
	BAD_CREDENTIALS(1002,"用户名或密码错误"),
	
	/**
	 * 用户被锁定
	 */
	USER_LOCKED(1003, "用户被锁定"),
	
	/**
	 * 用户登录过期
	 */
	REDENTIALS_Expired(1005, "您的账户登录过期,请重新登录"),
	;
	public int code;
	
	public String reasonPhrase;
	
	ResponseCode(int code, String reasonPhrase){
		this.code = code;
		this.reasonPhrase = reasonPhrase;
	}

	public int code() {
		return code;
	}

	public String reasonPhrase() {
		return reasonPhrase;
	}
	
}

package com.sxl.dto;

import java.io.Serializable;
import java.util.Map;
/**
 * 接口返回体
 * @author lin
 * 2017年8月19日15:54:08
 */
public class BaseDto  implements Serializable{
	private static final long serialVersionUID = 1L;
	/**
	 * 返回消息代码
	 */
	private Integer returnCode;
	/**
	 * 返回消息
	 */
	private String msg;
	/**
	 * 返回数据
	 */
	
	private Map<String, Object> returnData;
	
	public BaseDto() {
		super();
	}


	
	public BaseDto(Integer returnCode, String msg) {
		super();
		this.returnCode = returnCode;
		this.msg = msg;
	}
	public BaseDto(Integer returnCode, String msg, Map<String, Object> returnData) {
		this.returnCode = returnCode;
		this.msg = msg;
		this.returnData = returnData;
	}
	
	

	public Integer getReturnCode() {
		return returnCode;
	}



	public void setReturnCode(Integer returnCode) {
		this.returnCode = returnCode;
	}


	public String getMsg() {
		return msg;
	}



	public void setMsg(String msg) {
		this.msg = msg;
	}



	public Map<String, Object> getReturnData() {
		return returnData;
	}



	public void setReturnData(Map<String, Object> returnData) {
		this.returnData = returnData;
	}


	@Override
	public String toString() {
		return "{\"code\":" + returnCode + ",\"message\":\"" + msg + "\",\"returnData\":[]}";
	}
	
	
}

这样,前期的配置基本上就完成了,在实际应用过程中,为了加强系统的安全,我们配置一个认证中心服务,让用户都带着token访问页面,这也可以叫做单点登录。

六、配置认证中心-实现单点登录(此模块是参考别人的写的)

首先,先创建一个微服务,创建过程和上面步骤相同。
添加pom配置:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>clouddo</groupId>
    <artifactId>clouddo-auth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    
    <name>clouddo-auth</name>
    <description>认证微服务</description>

	<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.SR1</spring-cloud.version>
	</properties>
  <dependencies>
  <!-- 导入web相关的依赖 -->
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 为了启动和发现功能 -->
          <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--监控接口 -->
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- redis -->
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
       	 <dependency>
	        <groupId>redis.clients</groupId>
	        <artifactId>jedis</artifactId>
			</dependency>
			<dependency>
			    <groupId>org.springframework.data</groupId>
			    <artifactId>spring-data-commons</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>
          <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-data</artifactId>
        </dependency>
        <!-- log日志打印 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
            <scope>compile</scope>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
         <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- 通过简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 Java 代码的工具 -->
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
            <scope>provided</scope>
        </dependency>
 		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
         <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
         <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
       	<!-- config client端需依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency> 
        <!-- 引用公共类common -->
         <dependency>
        	<groupId>com.sxl</groupId>
        	<artifactId>clouddo-common</artifactId>
        	<version>0.0.1-SNAPSHOT</version>
        </dependency>
  </dependencies>
  
      <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  
</project>



然后在java目录下创建一个名为com.sxl.entity的包,在包中添加数据库中用户表的类,字段自己定义,我们这里起名为Member.java,代码如下:

package com.sxl.config.entity;


public class Member{

	private String UserID;
	private String UserAccount;
	private String Password;
	private String UserName;
	public String getUserID() {
		return UserID;
	}
	public void setUserID(String userID) {
		UserID = userID;
	}
	public String getUserAccount() {
		return UserAccount;
	}
	public void setUserAccount(String userAccount) {
		UserAccount = userAccount;
	}
	public String getPassword() {
		return Password;
	}
	public void setPassword(String password) {
		Password = password;
	}
	public String getUserName() {
		return UserName;
	}
	public void setUserName(String userName) {
		UserName = userName;
	}

	
}

然后在resources下创建新包mapper、dao,在里面创建一个名为MemberMapper.xml、MemberDao.java

mapper代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sxl.dao.MemberDao">

    <resultMap id="baseResultMap" type="com.sxl.config.entity.Member">
        <id property="UserID" column="UserID" jdbcType="VARCHAR" />
        <id property="UserAccount" column="UserAccount" jdbcType="VARCHAR" />
        <id property="Password" column="Password" jdbcType="VARCHAR" />
        <id property="UserName" column="UserName" jdbcType="VARCHAR" />
    </resultMap>

    <sql id="normalItems">
        id,member_name
    </sql>

    <select id="findByMemberName" parameterType="java.lang.String" resultType="com.sxl.config.entity.Member">
        select * from TB_UserInfo where UserAccount = #{memberName,jdbcType=VARCHAR}
    </select>


</mapper>

dao层代码如下:

package com.sxl.config.dao;

import org.apache.ibatis.annotations.Mapper;

import com.sxl.config.entity.Member;

@Mapper
public interface MemberDao {
	 Member findByMemberName(String memberName);
}

然后创建service层,创建MyUserDetialService.java,代码如下:

package com.sxl.config.service;
import java.util.HashSet;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.sxl.config.dao.MemberDao;
import com.sxl.config.entity.Member;

/**
 * 〈自定义UserDetailService〉
 * 自定义认证逻辑
 */
@Service
public class MyUserDetailService implements UserDetailsService{
	
	  @Autowired
	    private MemberDao memberDao;

	    @Override
	    public UserDetails loadUserByUsername(String memberName) throws UsernameNotFoundException {
	        Member member = memberDao.findByMemberName(memberName);
	        if (member == null) {
	            throw new UsernameNotFoundException(memberName);
	        }
	        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
	        // 可用性 :true:可用 false:不可用
	        boolean enabled = true;
	        // 过期性 :true:没过期 false:过期
	        boolean accountNonExpired = true;
	        // 有效性 :true:凭证有效 false:凭证无效
	        boolean credentialsNonExpired = true;
	        // 锁定性 :true:未锁定 false:已锁定
	        boolean accountNonLocked = true;
	/*        for (Role role : member.getRoles()) {
	            //角色必须是ROLE_开头,可以在数据库中设置
	            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRoleName());
	            grantedAuthorities.add(grantedAuthority);
	            //获取权限
	            for (Permission permission : role.getPermissions()) {
	                GrantedAuthority authority = new SimpleGrantedAuthority(permission.getUri());
	                grantedAuthorities.add(authority);
	            }
	        }*/
	        User user = new User(member.getUserAccount(), member.getPassword(),
	                enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);
	        return user;
	    }
}

注意这里所有的引用都是引用的org.springframework.security.core.userdetails里的。

然后创建controller层,添加MemberController类,代码如下:

package com.sxl.config.controller;

import java.security.Principal;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.sxl.config.ResponseCode;
import com.sxl.config.service.MyUserDetailService;
import com.sxl.dto.BaseDto;

public class MemberController {
	 @Autowired
	    private MyUserDetailService userDetailService;

	    @Autowired
	    private ConsumerTokenServices consumerTokenServices;

	    @RequestMapping("/member")
	    public Principal user(Principal member) {
	        //获取当前用户信息
	        return member;
	    }

	    @PostMapping(value = "/exit")
	    public BaseDto revokeToken(String access_token) {
	        //注销当前用户
	        Map<String, Object> map = new HashMap<>();
	        //返回默认值
	        map.put("result", "0");
	        if (consumerTokenServices.revokeToken(access_token)) {
	            return new BaseDto(ResponseCode.SUCCESS.code,"注销成功",map);
	        } else {
	            return new BaseDto(ResponseCode.EXPECTATION_FAILED.code,"注销失败",map);
	        }
	    }
}

添加一个名为error的包,创建MssWebResponseExceptionTranslator.java模块,此模块的作用是对异常进行捕捉、翻译,代码如下:

package com.sxl.config.error;


import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import java.io.IOException;

/**
 * 〈异常翻译〉
 */
public class MssWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {

        // Try to extract a SpringSecurityException from the stacktrace
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);

        if (ase != null) {
            return handleOAuth2Exception((OAuth2Exception) ase);
        }

        ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class,
                causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new MssWebResponseExceptionTranslator.UnauthorizedException(e.getMessage(), e));
        }

        ase = (AccessDeniedException) throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        if (ase instanceof AccessDeniedException) {
            return handleOAuth2Exception(new MssWebResponseExceptionTranslator.ForbiddenException(ase.getMessage(), ase));
        }

        ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer.getFirstThrowableOfType(
                HttpRequestMethodNotSupportedException.class, causeChain);
        if (ase instanceof HttpRequestMethodNotSupportedException) {
            return handleOAuth2Exception(new MssWebResponseExceptionTranslator.MethodNotAllowed(ase.getMessage(), ase));
        }

        return handleOAuth2Exception(new MssWebResponseExceptionTranslator.ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));

    }

    private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {

        int status = e.getHttpErrorCode();
        String error = e.getOAuth2ErrorCode();
        String summary = e.getSummary();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }

        ResponseEntity<OAuth2Exception> response = new ResponseEntity<OAuth2Exception>(e, headers, HttpStatus.valueOf(status));
        return response;

    }

    public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
        this.throwableAnalyzer = throwableAnalyzer;
    }

    @SuppressWarnings("serial")
    private static class ForbiddenException extends OAuth2Exception {

        public ForbiddenException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "access_denied";
        }

        @Override
        public int getHttpErrorCode() {
            return 403;
        }

    }

    @SuppressWarnings("serial")
    private static class ServerErrorException extends OAuth2Exception {

        public ServerErrorException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "server_error";
        }

        @Override
        public int getHttpErrorCode() {
            return 500;
        }

    }

    @SuppressWarnings("serial")
    private static class UnauthorizedException extends OAuth2Exception {

        public UnauthorizedException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "unauthorized";
        }

        @Override
        public int getHttpErrorCode() {
            return 401;
        }

    }

    @SuppressWarnings("serial")
    private static class MethodNotAllowed extends OAuth2Exception {

        public MethodNotAllowed(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "method_not_allowed";
        }

        @Override
        public int getHttpErrorCode() {
            return 405;
        }

    }
}

创建名为AuthorizationServerConfig的类,配置各种认证服务,代码如下:

package com.sxl.config.oauth;

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.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import com.sxl.config.error.MssWebResponseExceptionTranslator;
import com.sxl.config.service.MyUserDetailService;

import javax.sql.DataSource;

/**
 * 〈OAuth2认证服务器〉
 *  认证服务
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private MyUserDetailService userDetailService;

    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * 配置 oauth_client_details【client_id和client_secret等】信息的认证【检查ClientDetails的合法性】服务
     * 设置 认证信息的来源:数据库 (可选项:数据库和内存,使用内存一般用来作测试)
     * 自动注入:ClientDetailsService的实现类 JdbcClientDetailsService (检查 ClientDetails 对象)
     * 这个方法主要是用于校验注册的第三方客户端的信息,可以存储在数据库中,默认方式是存储在内存中,如下所示,注释掉的代码即为内存中存储的方式
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
       // clients.withClientDetails(clientDetails());
        //配置在内存中,也可以从数据库中获取
        clients.inMemory() // 使用in-memory存储
                .withClient("superadmin") // client_id   android
                .scopes("read")
                .secret("jzd")  // client_secret   android
                .authorizedGrantTypes("password", "authorization_code", "refresh_token") // 该client允许的授权类型
                .and()
                .withClient("webapp") // client_id
                .scopes("read")
                //.secret("webapp")  // client_secret
                .authorizedGrantTypes("implicit")// 该client允许的授权类型
                .and()
                .withClient("browser")
                .authorizedGrantTypes("refresh_token", "password")
                .scopes("read");



    }
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Bean
    public WebResponseExceptionTranslator webResponseExceptionTranslator(){
        return new MssWebResponseExceptionTranslator();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                .userDetailsService(userDetailService)
                .authenticationManager(authenticationManager);
        endpoints.tokenServices(defaultTokenServices());
        //认证异常翻译
       // endpoints.exceptionTranslator(webResponseExceptionTranslator());
    }

    /**
     * <p>注意,自定义TokenServices的时候,需要设置@Primary,否则报错,</p>
     * 自定义的token
     * 认证的token是存到redis里的
     * @return
     */
    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        //tokenServices.setClientDetailsService(clientDetails());
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60*60*12);
        // refresh_token默认30天
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }
}

创建名为RedisTokenStore的类,重写RedisTokenStore,原因是spring5.0修改了一些方法,导致不兼容,需要重写全部的set()为stringCommands().set(),代码如下:


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 java.util.*;

/**
 * 〈重写RedisTokenStore〉
 *  spring5.0修改了一些方法,导致不兼容,需要重写全部的set()为stringCommands().set()
 */
public class RedisTokenStore 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 RedisTokenStore(RedisConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }
 
    public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
        this.authenticationKeyGenerator = authenticationKeyGenerator;
    }
 
    public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) {
        this.serializationStrategy = serializationStrategy;
    }
 
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
 
    private RedisConnection getConnection() {
        return this.connectionFactory.getConnection();
    }
 
    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 byte[] serialize(String string) {
        return this.serializationStrategy.serialize(string);
    }
 
    private String deserializeString(byte[] bytes) {
        return this.serializationStrategy.deserializeString(bytes);
    }
 
    @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();
        }
    }
 
    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);
    }
 
    @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);
        }
    }
 
    @Override
    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);
    }
}

创建名为ResourceServerConfig的类,目的是配置资源服务器,代码如下:

import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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 javax.servlet.http.HttpServletResponse;

/**
 * 〈资源认证服务器〉
 *  配置资源服务器
 */
@Configuration
@EnableResourceServer
@Order(3)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .requestMatchers().antMatchers("/api/**")
                .and()
                .authorizeRequests()
                .antMatchers("/api/**").authenticated()
                .and()
                .httpBasic();
    }
}

接下来进行security配置
创建名为NoEncryptPasswordEncoder的类,目的是自定义无加密密码验证,重写PasswordEncoder,密码是不加密的,加密的话 使用 BCryptPasswordEncoder。
代码如下:

package com.sxl.config.security;

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

/**
 * 〈自定义无加密密码验证〉
 * 重写了PasswordEncoder  密码是不加密的
 * 加密的话 使用 BCryptPasswordEncoder
 */
public class NoEncryptPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {
        return (String) charSequence;
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        //密码对比 密码对 true 反之 false
        //CharSequence 数据库中的密码
        //s 前台传入的密码
        return s.equals((String) charSequence);
    }
}

创建名为SecurityConfig的类,目的是配置Spring Security,代码如下:

package com.sxl.config.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.sxl.config.service.MyUserDetailService;

/**
 * 〈security配置〉
 * 配置Spring Security
 * ResourceServerConfig 是比SecurityConfig 的优先级低的
 */
@Configuration
@EnableWebSecurity
@Order(2)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailService userDetailService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        //return new BCryptPasswordEncoder();
        return new NoEncryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/oauth/**")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated()
                .and()
                .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
    }

    /**
     * 不定义没有password grant_type,密码模式需要AuthenticationManager支持
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

接下来,创建一个启动类ClouddoAuthApplication.java,用来启动这个微服务,代码如下:

package com.sxl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@SpringBootApplication
@EnableEurekaClient
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ClouddoAuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(ClouddoAuthApplication.class, args);
        System.out.println("ヾ(◍°∇°◍)ノ゙    认证中心启动成功      ヾ(◍°∇°◍)ノ゙");
    }

}

这样,微服务的类就写好了,下面来写微服务的配置。
在resources中新建一个config包,添加名为mybatis-config.xml的配置文件,代码如下:

<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer" />
        <typeAlias alias="Long" type="java.lang.Long" />
        <typeAlias alias="HashMap" type="java.util.HashMap" />
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
        <typeAlias alias="ArrayList" type="java.util.ArrayList" />
        <typeAlias alias="LinkedList" type="java.util.LinkedList" />
        <typeAlias alias="Member" type="com.sxl.config.entity.Member" />
    </typeAliases>
</configuration>

创建一个lib包,引入sqljdbc的jar包,这个可以在网上自己下载。
在这里插入图片描述
然后右键这个微服务Build Path-Config Build Path-Add JARs-选择这个项目下的这个sqljdbc jar包。

添加bootstrap.yml文档,代码如下:

spring:
  application:
    name: clouddo-auth #服务名称
  cloud: 
    config: 
      uri: http://192.168.0.110:9002 #配置中心的地址
      enabled: true #开启配置
      profile: dev  #版本
      name: clouddo-auth #配置中心的配置文件名称
      label: master  #分支名称

同时不要忘了在配置中心里,添加上这个微服务的配置,创建名为clouddo-auth-dev.yml的文档,代码如下:

server:
  port: 9004
spring:
  application:
    name: clouddo-auth
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 5000
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1
  datasource:
    url: jdbc:sqlserver://115.28.107.219:1433;database=EnterpriseManufacturingSystem;
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    username: sa
    password: 89883365aA
    initial-size: 2 
    min-idle: 1 
    max-active: 2 
    max-wait: 60000
    time-between-eviction-runs-millis: 6000
    min-evictable-idle-time-millis: 300000
    validation-query: SELECT 1 FROM DUAL 
    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 


eureka:
  instance:
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 10 
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${spring.application.instance_id:${server.port}}
  client:
    service-url:
      defaultZone:  http://192.168.0.110:9001/eureka/

mybatis:
  config-location: classpath:config/mybatis-config.xml
  mapper-locations: classpath:mapper/*.xml
logging:
  level: 
    com.sxl.mapper: debug

这样,认证中心就配置完成了,别的服务想要使用认证中心,需要做以下几个修改(以zuul网关微服务为例):

  1. 添加相关jar的引入
  <!-- 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>
  1. 在java下创建config包,新建SecurityConfig.java,代码如下:
package com.sxl.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
@Order(99)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
    }
}

同时给zuul网关添加一个拦截器,对访问进行过滤拦截,新建包AccessFilter,添加一个AccessFilter类,代码如下:

package com.sxl.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import java.util.Set;

/**
 * @version V1.0
 * @Author bootdo 1992lcg@163.com
 */
public class AccessFilter extends ZuulFilter {


    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 10000;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }


    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        Set<String> headers = (Set<String>) ctx.get("ignoredHeaders");
        headers.remove("authorization");
        return null;
    }


}

  1. 在配置中心微服务中对网关的配置进行修改,代码如下:
server:
  port: 9003
eureka:
  instance:
    #    使用IP注册
    lease-renewal-interval-in-seconds: 10 # 每间隔1s,向服务端发送一次心跳,证明自己依然”存活“
    lease-expiration-duration-in-seconds: 10 # 告诉服务端,如果我2s之内没有给你发心跳,就代表我“死”了,将我踢出掉。
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${spring.application.instance_id:${server.port}}
  client:
    service-url:
      defaultZone:  http://192.168.0.110:9001/eureka/
spring:
  servlet:
    multipart:
      max-file-size: 100Mb
      max-request-size: 100Mb
zuul:
  host:
      connections: 500
      connect-timeout-millis: 60000
      socket-timeout-millis: 60000
  routes:
 # 认证中心微服务
    clouddo-auth:
      service-id: clouddo-auth
      path: /auth/**
#---------------------OAuth2---------------------
security:
  oauth2:
    client:
      access-token-uri: http://192.168.0.110:${server.port}/auth/oauth/token
      user-authorization-uri: http://192.168.0.110:${server.port}/auth/oauth/authorize
      client-id: web
    resource:
      user-info-uri:  http://192.168.0.110:${server.port}/auth/api/member
      prefer-token-info: false
#----------------------超时配置-------------------
ribbon:
  ReadTimeout: 3500
  ConnectTimeout: 1000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0
  eureka:
  #是否开启超时熔断
    enabled: true
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 50000

因为这些token是临时存在redis中的,所以还要下载一个redis,然后启动。

@echo off
title redis-server
set ENV_HOME="C:\redis"
C:
color 0a
cd %ENV_HOME%
redis-server redis.windows.conf
exit

在这里插入图片描述
这样,认证中心的所有配置就配置完成了,服务启动后,显示如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值