使用oauth2实现微服统一认证授权。通过向oauth2认证服务器发送请求获取token。
,然后携带token访问其他微服务,此token在其他微服务是信任的(即是鉴权验证token是否可用)
模块:
eureka:服务注册和发现的基本模块
zuul:边界网关(所有微服务都在它之后)
oauth2: OAUTH2认证授权中心
service :普通微服务,用来验证认证和鉴权
1.1 添加基础pom.xml文件:
4.0.0
springcloud-oauth2
springcloud-oauth2
pom
1.0-SNAPSHOT
oauth-server
api-gateway
eureka
provide-service
org.springframework.boot
spring-boot-starter-parent
2.0.3.RELEASE
1.8
UTF-8
UTF-8
-Dfile.encoding=UTF-8
true
org.springframework.cloud
spring-cloud-dependencies
Finchley.SR2
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-openfeign
org.projectlombok
lombok
1.16.20
org.jolokia
jolokia-core
1.2 添加oauth-server的pom.xml文件
springcloud-oauth2
springcloud-oauth2
1.0-SNAPSHOT
4.0.0
oauth-server
redis.clients
jedis
2.9.0
com.google.code.gson
gson
2.8.2
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.security
spring-security-data
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-data-redis
redis.clients
jedis
io.lettuce
lettuce-core
代码目录:
1.3 添加认证的配置类,使用Redis用来存储token
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisConnectionFactory connectionFactory;
@Autowired
private UserDetailsService userDetailsService;
@Bean
public MyRedisTokenStore tokenStore() {
return new MyRedisTokenStore(connectionFactory);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)//若无,refresh_token会有UserDetailsService is required错误
.tokenStore(tokenStore());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("wf")
.scopes("wf")
.secret("wf")
.authorizedGrantTypes("password", "authorization_code", "refresh_token");
}
Resource服务配置类
auth-server提供user信息,所以auth-server也是一个Resource Server
/**
* Created by wf on 2019/03/15
*/
@Configuration
@EnableResourceServer
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()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
/**
* @author wufei
* @create 2019-03-13 16:31
**/
@RestController
public class UserController {
@GetMapping("/user")
public Principal user(Principal user){
return user;
}
}
1.4 添加安全配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
return new DomainUserDetailsService();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();//new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
//不定义没有password grant_type
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
1.5 直接使用RedisTokenStore存储token会出现NoSuchMethodError RedisConnection.set([B[B)V错误
解决方案:自己编写一个MyRedisTokenStore,复制RedisTokenStore类中代码,并将代码中conn.set(accessKey,
serializedAccessToken)修改为conn.stringCommands().set(accessKey,
serializedAccessToken);
1.6 添加操作db类
/**
* @author wufei
* @create 2019-03-14 9:15
**/
@Data
public class User {
private int id;
private String username;
private String password;
}
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE username = #{username}")
User findByUserName(@Param("username") String username);
}
@Service("userDetailsService")
@Slf4j
public class DomainUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findByUserName(username);
List extends GrantedAuthority> authorities = new ArrayList();
return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),authorities);
}
}
1.7 添加OauthServerApplication启动类
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.wf.oauth.db")
public class OauthServerApplication implements CommandLineRunner {
private final UserMapper userMapper;
public OauthServerApplication(UserMapper userMapper) {
this.userMapper = userMapper;
}
public static void main(String[] args) {
SpringApplication.run(OauthServerApplication.class,args);
}
@Override
public void run(String... args) {
User user = this.userMapper.findByUserName("admin");
if(user != null){
System.out.println("================: "+user.getUsername());
}
}
}
1.8 配置文件:
server:
port: 8082
tomcat:
accept-count: 1000
max-threads: 1000
max-connections: 2000
spring:
application:
name: auth-server
datasource:
url: jdbc:mysql://192.168.3.1:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
username: xx
driver-class-name: com.mysql.jdbc.Driver
password: xx
redis:
host: 192.168.3.1
port: 6380
password: xinleju
database: 15
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
logging:
level:
org:
springframework:
security: DEBUG
wf:
oauth:
clientid: wf_oauth
secret: wf_secret
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/
instance:
prefer-ip-address: true #显示服务器IP
provide-service是一个简单的微服务,使用auth-server进行认证授权,在它的配置文件指定用户信息在auth-server的地址即可:
info:
version: "v1"
name: "provide"
management:
endpoints:
web:
exposure:
include: '*'
#端口号
server:
port: 8081
tomcat:
max-threads: 200
spring:
application:
name: provide-server
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/
instance:
prefer-ip-address: true #显示服务器IP
security:
oauth2:
resource:
id: service
user-info-uri: http://localhost:9999/uaa/user # 使用auth-server进行认证授权,
prefer-token-info: false
2.1 配置资源服务
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/test/test2").permitAll().and() //不需要授权认证 .csrf().disable()
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@RestController
@RequestMapping("test")
public class UserController {
@RequestMapping("test1")
public Object test(){
return "ok";
}
}
2.2 ProvideApplication启动类:
@SpringCloudApplication
public class ProvideApplication {
public static void main(String[] args) {
SpringApplication.run(ProvideApplication.class,args);
System.out.println("ProvideApplication 启动Ok!");
}
}
2.3 添加pom配置文件
org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-oauth2
3.1 添加pom文件
org.springframework.cloud
spring-cloud-starter-netflix-zuul
com.squareup.okhttp3
okhttp
org.springframework.retry
spring-retry
org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-oauth2
3.2 关闭csrf并开启Oauth2 client支持
/**
* 关闭csrf跨站请求伪造并开启Oauth2 client支持
* @author wufei
* @create 2019-03-15 9:16
**/
@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
3.3 配置文件
info:
version: "v1"
name: "zuul"
management:
endpoints:
web:
exposure:
include: '*'
#端口号
server:
port: 9999
tomcat:
max-threads: 200
spring:
application:
name: zuul
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/
instance:
prefer-ip-address: true #显示服务器IP
zuul:
routes:
uaa:
sensitiveHeaders:
serviceId: auth-server
provide:
sensitiveHeaders:
serviceId: provide-server
add-proxy-headers: true
security:
oauth2:
client:
access-token-uri: http://localhost:9999/uaa/oauth/token #网关的地址
user-authorization-uri: http://localhost:9999/uaa/oauth/authorize
client-id: wf
resource:
user-info-uri: http://localhost:9999/uaa/user
prefer-token-info: false
3.4添加启动类ApiGateWayApplication
开启支持Sso
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
@EnableOAuth2Sso
public class ApiGateWayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGateWayApplication.class, args);
}
}
分别启动eureka、zuul、auth-server、provide服务
4.1 客户端Postman调用
使用Postman通过网关http://localhost:9999/uaa/oauth/token发送请求获得access_token,用户名和密码分别是admin、admin,授权方式为password
4.2 客户端httpclient调用
/**
* @author wufei
* @create 2019-03-14 14:38
**/
public class TokenTest {
private static HttpClientContext context = HttpClientContext.create();
public static void main(String[] args) {
String loginUrl = "http://localhost:9999/uaa/oauth/token";
String username = "admin";
String password = "admin";
String scope = "wf";
String clientId = "wf";
String clientSecret = "wf";
CloseableHttpClient httpClient = HttpClientPool.getHttpClient();//HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();
HttpPost httpPost = new HttpPost(loginUrl);
List values = new ArrayList();
values.add(new BasicNameValuePair("grant_type", "password"));
values.add(new BasicNameValuePair("username", username));
values.add(new BasicNameValuePair("password", password));
values.add(new BasicNameValuePair("scope", scope));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(values, Consts.UTF_8);
httpPost.setEntity(entity);
CloseableHttpResponse response = null;
try {
httpPost.setHeader("authorization", "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes("utf-8")));
String body = "";
response = httpClient.execute(httpPost, context);
HttpEntity httpEntity = response.getEntity();
if (httpEntity != null) {
try {
body = EntityUtils.toString(httpEntity, Consts.UTF_8);
System.out.println("=======================body: "+body);
EntityUtils.consume(httpEntity);
} catch (IOException e) {
}
}
} catch (IOException e) {
} finally {
try {
response.close();
} catch (Exception e) {
}
}
}
}
返回结果:
=======================body: {"access_token":"277ab3a0-f415-4c03-b48b-1e590324afef","token_type":"bearer","refresh_token":"63243753-5ed3-421c-bf02-096f65bc8c9c","expires_in":36738,"scope":"wf"}
4.3 如果有些接口不需要oauth2认证呢?
解决方案:在ResourceServerConfig配置上添加如下:
http.authorizeRequests().antMatchers("/test/test2").permitAll().and() //不需要授权认证
现在我们provide-service服务有两个接口test1和test2,test2设置了不需要验证,
现在我们直接访问test2接口,
然后在访问test1,出现需要授权访问401
最后我们携带token访问test1接口,访问成功
最后:模拟用户登录
@RequestMapping("/login")
public String login(){
//1验证用户和密码是否正确
//2.清空原来的用户token
//3.请求认证信息,颁发token
//4.返回用户信息和token信息
return null;
}
git源码地址:https://github.com/wflovejava/springcloud-oauth2.git