添加小编微信进入java学习交流群,小编微信:372787553,备注进群
本文是基于Redis实现的,如果您想基于数据库实现,请移步到SpringCloud+OAuth2+JDBC实现统一权限管理
OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 2.0即完全废止了OAuth1.0。 OAuth
2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限
在详细讲解OAuth 2.0之前,需要了解几个专用名词。它们对读懂后面的讲解,尤其是几张图,至关重要。
(1) Third-party application:第三方应用程序,
(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商",
(3)Resource Owner:资源所有者
(4)User Agent:用户代理,本文中就是指浏览器。
(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
知道了上面这些名词,就不难理解,OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。
以上摘自大佬的博客,接下来我们就开始实战
本篇文章篇幅过程,还请耐心观看!如有不适,请多喝烫水!
javayh-eureka– 服务注册中心
javayh-oauth – 认证服务器
javayh-shop – 资源服务器
javayh-zuul – 路由中心
这里主要说认证证服务器,路由的配置
server:
port: 8091
spring:
application:
name: javayh-zuul
#--------------------eureka---------------------
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:8090/eureka/
#--------------------Zuul-----------------------
zuul:
routes:
member:
path: /member/**
serviceId: javayh-shop
sensitiveHeaders: "*"
auth:
path: /auth/**
serviceId: javayh-oauth
sensitiveHeaders: "*"
retryable: false
ignored-services: "*"
ribbon:
eager-load:
enabled: true
host:
connect-timeout-millis: 60000
socket-timeout-millis: 60000
add-proxy-headers: true
#---------------------OAuth2---------------------
security:
oauth2:
client:
access-token-uri: http://localhost:${server.port}/auth/oauth/token
user-authorization-uri: http://localhost:${server.port}/auth/oauth/authorize
client-id: web
resource:
user-info-uri: http://localhost:${server.port}/auth/javayh/member
prefer-token-info: false
#----------------------超时配置-------------------
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
MaxAutoRetries: 2
MaxAutoRetriesNextServer: 2
eureka:
enabled: true
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 60000
4.0.0
com.javayh
javayh-oauth2
0.0.1-SNAPSHOT
com.javayh
javayh-zuul
0.0.1-SNAPSHOT
javayh-zuul
javayh-zuul
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.cloud
spring-cloud-starter-security
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-maven-plugin
@Configuration
@EnableWebSecurity
@Order(99)//必加
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 禁止csrf
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
摘取自某位大佬的,讲解很详细
server:
port: 8092
spring:
application:
name: javayh-oauth
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/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
username: root
password: 1219320
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:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:8090/eureka/
mybatis:
type-aliases-package: com.javayh.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
4.0.0
com.javayh
javayh-oauth2
0.0.1-SNAPSHOT
com.javayh
javayh-oauth
0.0.1-SNAPSHOT
javayh-oauth
ojavayh-oauth
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.cloud
spring-cloud-starter-security
org.springframework.boot
spring-boot-starter-data-redis
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
org.springframework.boot
spring-boot-starter-actuator
mysql
mysql-connector-java
5.1.46
com.alibaba
druid
1.1.9
log4j
log4j
1.2.17
org.springframework.boot
spring-boot-maven-plugin
接下来是重点配置
@Slf4j
@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) {
log.info("连接异常"+e.getMessage());
}
datasource.setConnectionProperties(connectionProperties);
return datasource;
}
@Bean
public FilterRegistrationBean statFilter() {
//创建过滤器
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//设置过滤器过滤路径
filterRegistrationBean.addUrlPatterns("/*");
//忽略过滤的形式
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
@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()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.withClientDetails(clientDetails());
clients.inMemory()
.withClient("android")
.scopes("read")
.secret("android")
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.and()
.withClient("webapp")
.scopes("read")
.authorizedGrantTypes("implicit")
.and()
.withClient("browser")
.authorizedGrantTypes("refresh_token", "password")
.scopes("read");
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator(){
return new JavaYhWebException();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.userDetailsService(userDetailService)
.authenticationManager(authenticationManager);
endpoints.tokenServices(defaultTokenServices());
//认证异常翻译
endpoints.exceptionTranslator(webResponseExceptionTranslator());
}
/**
* 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,
* @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;
}
}
@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("/javayh/**")
.and()
.authorizeRequests()
.antMatchers("/javayh/**").authenticated()
.and()
.httpBasic();
}
}
@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();
}
}
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);
}
}
特别强调:
本文中用的RedisTokenStore是重写,负责会报RedisConnection set时错误,一下是区别
左侧为重写的后的方法,在Spring5以后需要重写
我们给此路径配置的hello权限,但admin并无hello的权限
至此SpringCloud + OAith2统一权限验证就完成了!
同时欢迎大家关注小编的公众号《JAVA有货》
联系小编。微信:372787553,带您进群互相学习
左侧小编微信,右侧获取免费资料