一、环境要求
eureka注册中心
gateway网关
maven3.0+
jdk1.8
redis 3.2+
mysql数据
postman测试工具
项目为Maven结构
二、源码下载地址
源码下载地址:GitHub
springCloud完整版开源项目博客详细说明请点击这里
springCloud入门级教程及相关疑难杂症干货请点击这里
三、参考文献
建议做好前期的知识储备
大神的oauth2.0讲解:阮一峰-理解OAuth 2.0
oauth2.0官方文档开发指南:点击这里
security入门博客:点击这里
security简单的登录:点击这里
主要参看的博客:必看
我是在他这个博客基础上改版的。因为他用的是zuul网关,我换成了gateway。拾人牙慧了 哈哈
四、项目预览截图说明
项目为Maven结构
eureka 注册中心(高可用版本)
gateway 网关gateway(不是zuul)
oauth-common 公共java代码 jar包依赖
oauth-member 用于测试的oauth的一个项目
oauth-server 核心项目,认证授权
这里粘贴下公共的父级依赖:接下来的项目会用到:
<?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.cloud.mc</groupId>
<artifactId>cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>cloud</name>
<description>Demo project for Spring Boot</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.SR2</spring-cloud.version>
<fastjson.version>1.2.47</fastjson.version>
<commons-lang.version>2.6</commons-lang.version>
<mybatis.version>1.3.2</mybatis.version>
<mysql.version>5.1.46</mysql.version>
<druid.version>1.1.10</druid.version>
<lombok.version>1.16.20</lombok.version>
<spring.security.version>4.1.0.RELEASE</spring.security.version>
<log4j.version>1.2.16</log4j.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/>
</parent>
<modules>
<module>eureka</module>
<module>gateway</module>
<module>config</module>
<module>zipkin-server</module>
<module>producer7001</module>
<module>producer7002</module>
<module>consumer6001</module>
<module>consumer6002</module>
<module>oauth-common</module>
<module>oauth-server</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<scope>compile</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
五、oauth2.0认证授权项目
5.1 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.cloud.mc</groupId>
<artifactId>cloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>oauth-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth-server</name>
<packaging>war</packaging>
<description>oauth-授权模块</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.cloud.mc</groupId>
<artifactId>oauth-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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.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.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意依赖父级项目
5.2 yml配置及启动类
server:
port: 1202
spring:
application:
name: oauth-server
redis:
database: 0
host: localhost
port: 6379
password:
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/eshop_member?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
username: root
password: 123456
druid:
initialSize: 5 #初始化连接大小
minIdle: 5 #最小连接池数量
maxActive: 20 #最大连接池数量
maxWait: 60000 #获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
minEvictableIdleTimeMillis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 from DUAL #测试连接
testWhileIdle: true #申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性
testOnBorrow: false #获取连接时执行检测,建议关闭,影响性能
testOnReturn: false #归还连接时执行检测,建议关闭,影响性能
poolPreparedStatements: false #是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
maxPoolPreparedStatementPerConnectionSize: 20 #开启poolPreparedStatements后生效
filters: stat,wall,log4j #配置扩展插件,常用的插件有=>stat:监控统计 log4j:日志 wall:防御sql注入
connectionProperties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' #通过connectProperties属性来打开mergeSql功能;慢SQL记录
eureka:
client:
service-url:
defaultZone: http://peer1:8000/eureka/,http://peer2:8001/eureka/,http://peer3:8002/eureka/
mybatis:
type-aliases-package: com.mc.common.entity
configuration:
map-underscore-to-camel-case: true #开启驼峰命名,l_name -> lName
jdbc-type-for-null: NULL
lazy-loading-enabled: true
aggressive-lazy-loading: true
cache-enabled: true #开启二级缓存
call-setters-on-nulls: true #map空列不显示问题
mapper-locations:
- classpath:mybatis/*.xml
启动类添加注解:
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan(“com.example.oauthserver.dao”)
5.3 认证配置与资源配置
资源认证配置:
package com.example.oauthserver.config.oauth;
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;
/**
* 〈资源认证服务器〉
*
* @author Curise
* @create 2018/12/13
* @since 1.0.0
*/
@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()
// or可以通过access_token访问,但是and不行;经过测试,应该是hasRole()方法出了问题,这里无法通过
// .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_ADMIN')")
.antMatchers("/api/**").authenticated()// 配置api访问控制,必须认证过后才可以访问
.and()
.httpBasic();
}
}
认证配置:
package com.example.oauthserver.config.oauth;
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;
/**
* 〈资源认证服务器〉
*
* @author Curise
* @create 2018/12/13
* @since 1.0.0
*/
@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()
// or可以通过access_token访问,但是and不行;经过测试,应该是hasRole()方法出了问题,这里无法通过
// .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_ADMIN')")
.antMatchers("/api/**").authenticated()// 配置api访问控制,必须认证过后才可以访问
.and()
.httpBasic();
}
}
5.4 自定义UserDetailsService
package com.example.oauthserver.service;
import com.example.oauthserver.dao.MemberDao;
import com.mc.common.entity.Member;
import com.mc.common.entity.Permission;
import com.mc.common.entity.Role;
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 java.util.HashSet;
import java.util.Set;
/**
* 〈自定义UserDetailService〉
*
* @author Curise
* @create 2018/12/13
* @since 1.0.0
*/
@Service("userDetailService")
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.getMemberName(), member.getPassword(),
enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);
return user;
}
}
5.5 security配置
package com.example.oauthserver.config.security;
import com.example.oauthserver.service.MyUserDetailService;
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;
/**
* 〈security配置〉
*
* @author Curise
* @create 2018/12/13
* @since 1.0.0
*/
@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();
}
}
5.6 自定义无加密密码验证
package com.example.oauthserver.config.security;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 〈自定义无加密密码验证〉
*
* @author Curise
* @create 2018/12/13
* @since 1.0.0
*/
public class NoEncryptPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return (String) charSequence;
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals((String) charSequence);
}
}
5.7 druid连接池配置
package com.example.oauthserver.config.druid;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.sql.SQLException;
/**
* 〈druid连接池配置〉
*
* @author Curise
* @create 2018/12/13
* @since 1.0.0
*/
@Configuration
public class DruidConfiguration {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.druid.initialSize}")
private int initialSize;
@Value("${spring.druid.minIdle}")
private int minIdle;
@Value("${spring.druid.maxActive}")
private int maxActive;
@Value("${spring.druid.maxWait}")
private int maxWait;
@Value("${spring.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.druid.validationQuery}")
private String validationQuery;
@Value("${spring.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.druid.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.druid.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.druid.maxPoolPreparedStatementPerConnectionSize}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${spring.druid.filters}")
private String filters;
@Value("{spring.druid.connectionProperties}")
private String connectionProperties;
@Bean
@Primary
public DataSource dataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(url);
datasource.setUsername(username);
//这里可以做加密处理
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
//configuration
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
try {
datasource.setFilters(filters);
} catch (SQLException e) {
}
datasource.setConnectionProperties(connectionProperties);
return datasource;
}
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//设置ip白名单
servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
//设置ip黑名单,优先级高于白名单
servletRegistrationBean.addInitParameter("deny", "192.168.0.19");
//设置控制台管理用户
servletRegistrationBean.addInitParameter("loginUsername", "root");
servletRegistrationBean.addInitParameter("loginPassword", "root");
//是否可以重置数据
servletRegistrationBean.addInitParameter("resetEnable", "false");
return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean statFilter() {
//创建过滤器
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//设置过滤器过滤路径
filterRegistrationBean.addUrlPatterns("/*");
//忽略过滤的形式
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
认证授权项目配置完成。
5.8 执行数据库脚本
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50726
Source Host : localhost:3306
Source Database : eshop_member
Target Server Type : MYSQL
Target Server Version : 50726
File Encoding : 65001
Date: 2019-12-20 11:50:01
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for clientdetails
-- ----------------------------
DROP TABLE IF EXISTS `clientdetails`;
CREATE TABLE `clientdetails` (
`appId` varchar(128) NOT NULL,
`resourceIds` varchar(256) DEFAULT