写这篇博客的时候看了网上很多Oauth2的案例,很多都集成了jwt,但是感觉这个东西实在是没有必要。
以下是自己参考网上博客,琢磨出来的代码,有待完善。
github:https://github.com/LI-DAI/mall_demo
mall-admin 模块
首先贴以下主要依赖
org.springframework.boot
spring-boot-starter-security
org.springframework.security.oauth.boot
spring-security-oauth2-autoconfigure
2.1.3.RELEASE
再看一下配置文件
server.port=2001
spring.application.name=mall-admin
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://10.0.93.60:3306/mall_demo?useUnicode=true&characterEncoding=utf8
#spring.datasource.druid.url=jdbc:mysql://localhost:3306/mall_demo?useUnicode=true&characterEncoding=utf8
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
#\u521D\u59CB\u5316\u8FDE\u63A5\u6C60\u5927\u5C0F
spring.datasource.druid.initial-size=1
#\u6700\u5927\u8FDE\u63A5
spring.datasource.druid.max-active=20
#\u7B49\u5F85\u8D85\u65F6\u65F6\u95F4
spring.datasource.druid.max-wait=30000
#\u6700\u5C0F\u8FDE\u63A5
spring.datasource.druid.min-idle=1
#\u9694\u591A\u4E45\u8FDB\u884C\u4E00\u6B21\u68C0\u6D4B \u68C0\u6D4B\u9700\u8981\u5173\u95ED\u7A7A\u95F2\u8FDE\u63A5 \u5355\u4F4D\u6BEB\u79D2
spring.datasource.druid.time-between-eviction-runs-millis=50000
#\u4E00\u4E2A\u8FDE\u63A5\u5728\u6C60\u4E2D\u6700\u5C0F\u7684\u751F\u5B58\u65F6\u95F4 \u5355\u4F4D\u6BEB\u79D2
spring.datasource.druid.min-evictable-idle-time-millis=50000
#\u9A8C\u8BC1\u8FDE\u63A5\u662F\u5426\u6709\u6548
spring.datasource.druid.validation-query=select 1 from dual
#\u5982\u679C\u7A7A\u95F2\u65F6\u95F4\u5927\u4E8EtimeBetweenEvictionRunsMillis\uFF0C\u5219\u6267\u884Cvalidation-query\u68C0\u6D4B\u662F\u5426\u6709\u6548
spring.datasource.druid.test-while-idle=true
#\u7533\u8BF7\u8FDE\u63A5\u65F6\u662F\u5426\u68C0\u6D4B
spring.datasource.druid.test-on-borrow=false
#\u5F52\u8FD8\u8FDE\u63A5\u65F6\u662F\u5426\u68C0\u6D4B
spring.datasource.druid.test-on-return=false
#\u6253\u5F00PsCache
spring.datasource.druid.pool-prepared-statements=true
#\u6307\u5B9APsCache\u5927\u5C0F
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
#\u914D\u7F6E\u76D1\u63A7\u7EDF\u8BA1\u62E6\u622A\u7684filters \u82E5\u4E0D\u914D\u7F6E \u65E0\u6CD5\u7EDF\u8BA1\u76D1\u63A7\u754C\u9762sql
spring.datasource.druid.filters=stat,wall
#\u5408\u5E76\u591A\u4E2ADruidDataSource\u7684\u76D1\u63A7\u6570\u636E
spring.datasource.druid.use-global-data-source-stat=true
#\u901A\u8FC7connectProperties\u5C5E\u6027\u6765\u6253\u5F00mergeSql\u529F\u80FD\uFF1B\u6162SQL\u8BB0\u5F55
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#JPA
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#redis
spring.redis.database=0
spring.redis.port=6379
spring.redis.host=10.0.93.60
spring.redis.password=
spring.redis.timeout=5000ms
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.max-active=8
#\u6587\u4EF6\u4E0A\u4F20\u8DEF\u5F84
upload.locations=d:/test
#security
#spring.security.user.name=root
#spring.security.user.password=123456
auth-server=http://localhost:2001/oauth
security.oauth2.client.client-id=client
security.oauth2.client.client-secret=secret
security.oauth2.client.access-token-uri=${auth-server}/token
security.oauth2.client.user-authorization-uri=${auth-server}/authorize
下面就是重点了
SecurityConfiguration.java
package com.mall.admin.configuration;
import com.mall.admin.entity.Permission;
import com.mall.admin.entity.User;
import com.mall.admin.repository.PermissionRepository;
import com.mall.admin.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author lidai
* @date 2019/7/9 11:06
* @since
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserRepository userRepository;
@Autowired
private PermissionRepository permissionRepository;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
}
@Bean
public UserDetailsService userDetailsService() {
return username -> {
User user = userRepository.findByUsername(username);
if (null == user) {
throw new IllegalArgumentException("Username : " + username + " not found !");
}
List permissions = permissionRepository.getPermissionsByUserId(user.getUserId());
Set perms = permissions.stream().map(Permission::getPerms).collect(Collectors.toSet());
List authorities = AuthorityUtils.createAuthorityList(perms.toArray(new String[]{}));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
};
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.formLogin()
// .successHandler((request, response, authentication) -> response.getWriter().write("congratulations ! login success ."))
// .and()
// .authorizeRequests()
// .antMatchers("/login").permitAll()
// .antMatchers(HttpMethod.OPTIONS).permitAll()
// .anyRequest().authenticated()
// .and()
// .exceptionHandling()
// .accessDeniedHandler((request, response, accessDeniedException) -> {
// response.setCharacterEncoding("UTF-8");
// Writer writer = response.getWriter();
// writer.write(JSONObject.toJSONString(Result.build().unauthorized()));
// writer.flush();
// });
//
// }
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/oauth/check_token", "/user/login");
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
AuthorizationServerConfiguration.java
/*
* Copyright (C), 2013-2019, 天津大海云科技有限公司
*/
package com.mall.admin.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.sql.DataSource;
/**
* @author lidai
* @date 2019/7/9 14:16
*
* 认证服务器
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//自动从数据库查数据
// clients.withClientDetails(detailsService());
clients.inMemory()
.withClient("browser")
.secret(passwordEncoder.encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.scopes("all");
// 注册回调地址
// .redirectUris("http://www.funtl.com");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager).userDetailsService(userDetailsService);
// DefaultTokenServices tokenServices = new DefaultTokenServices();
// tokenServices.setTokenStore(endpoints.getTokenStore());
// tokenServices.setSupportRefreshToken(true);
// tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
// tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// //1小时
// tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
// //1天
// tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));
// tokenServices.setReuseRefreshToken(false);
// endpoints.tokenServices(tokenServices);
}
@Bean
public TokenStore tokenStore() {
// return new JdbcTokenStore(dataSource);
return new RedisTokenStore(redisConnectionFactory);
}
// @Bean
// public ClientDetailsService detailsService() {
// return new JdbcClientDetailsService(dataSource);
// }
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
}
ResourceServerConfiguration.java
/*
* Copyright (C), 2013-2019, 天津大海云科技有限公司
*/
package com.mall.admin.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* @author lidai
* @date 2019/7/10 15:53
* @since
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
//请求权限配置
.authorizeRequests()
.antMatchers("/user/login").permitAll()
.antMatchers("/oauth/*").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated();
}
}
配置类就这样了,代码写的不是太好,将就看吧,以后还会不断完善填充
下面是另一个模块
mall-market 模块
这个模块只有一资源服务器,访问的时候会直接请求到认证服务器验证是否登录
配置文件
server:
port: 2002
spring:
application:
name: mall-market
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.0.93.60:3306/mall_demo?useUnicode=true&characterEncoding=utf8
# url: jdbc:mysql://localhost:3306/mall_demo?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
initial-size: 1
max-active: 20
max-wait: 30000
min-idle: 1
time-between-eviction-runs-millis: 50000
min-evictable-idle-time-millis: 50000
validation-query: select 1 from dual
test-on-borrow: false
test-on-return: false
test-while-idle: true
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filter: stat,wall
use-global-data-source-stat: true
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
jpa:
database: mysql
show-sql: true
hibernate:
ddl-auto: update
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
redis:
database: 0
port: 6379
host: 10.0.93.60
password:
timeout: 5000ms
jedis:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 1
#security
auth-server: http://localhost:2001/oauth
security:
oauth2:
client:
client-id: browser
client-secret: secret
access-token-uri: ${auth-server}/token
user-authorization-uri: ${auth-server}/authorize
resource:
token-info-uri: ${auth-server}/check_token
ResourceServerConfiguration.java
package com.mall.market.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* @author lidai
* @date 2019/7/10 15:53
* @since
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
//请求权限配置
.authorizeRequests()
.antMatchers("/user/login").permitAll()
.antMatchers("/oauth/*").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated();
}
}
postman 测试
可以直接访问 http://localhost:2001/oauth/token 获取token
添加Authorization 参数,即为我们定义再内存中的client与secret
获取成功之后,我们看看redis中的数据
我使用了两个用户进行登录,分别是user和admin
成功获取token之后,可以使用此token进行访问
示例,访问market模块下接口
这里需要注意,需要再access_token前加上token类型:bearer
到这里基本上单点登录的功能就已经实现,若有错误,欢迎指正。