OAuth 2 有四种授权模式,分别是授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials),具体 OAuth2 是什么,可以参考这篇文章(http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)
本篇先介绍密码模式实现流程图,以微信授权登录为例。第三方应用获取微信用户信息。在微信授权登录的模式,需要到微信官方备案该应用。
1.用户通过手机微信扫码授权后台会发起(https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http:open.winning.com/index&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect)访问微信授权服务器
2.微信服务器验证通过后跳转(http:open.winning.com/index?code=DX3FCW)第三方应用并带有code参数
3.第三方服务获取后code参数后,继续请求微信认证服务器((https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID
&secret=SECRET&code=DX3FCW&grant_type=authorization_code))获取token。通过验证和后返回token。
4.第三方服务获取token后,向微信用户信息服务发起请求,获取用户信息。第三方成功获取用户信息后。用户登录第三方应用成功。
在一台电脑实现双注册中心需要修改本机hosts文件,如下,添加127.0.0.1 peer1 127.0.0.1 peer2
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class GoodluckEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(GoodluckEurekaApplication.class, args);
}
}
server.port=1111
spring.application.name=hello-service1
#关闭保护机制
eureka.server.enable-self-preservation=false
eureka.instance.hostname=peer1
#由于该应用是注册中心,false:代表不向注册中心注册自己;true:代表注册自己
eureka.client.register-with-eureka=false
#是否启动检测服务,由于注册中心的职责是维护服务实例,所以它不需要检服务
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://peer2:1112/eureka/
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
com.goodluck
goodluck-eureka
0.0.1-SNAPSHOT
goodluck-eureka
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.cloud
spring-cloud-dependencies
Finchley.RELEASE
pom
import
org.springframework.boot
spring-boot-maven-plugin
spring.application.name=eureka-server
server.port=1112
#关闭保护机制
eureka.server.enable-self-preservation=false
eureka.instance.hostname=peer2
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/
1.这是我的项目结构
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* JwtTokenConfig
*
* @author fengzheng
* @date 2019/10/12
*/
@Configuration
public class JwtTokenConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("dev");
return accessTokenConverter;
}
}
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2020-06-10.
*/
@Slf4j
@Component(value = "kiteUserDetailsService")
public class KiteUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
private final Logger logger = LoggerFactory.getLogger(KiteUserDetailsService.class);
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("usernameis:" + username);
// 查询数据库操作
if(!username.equals("admin")){
throw new UsernameNotFoundException("the user is not found");
}else{
// 用户角色也应在数据库中获取
String role = "ROLE_ADMIN";
List authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(role));
// 线上环境应该通过用户名查询数据库获取加密后的密码
String password = passwordEncoder.encode("123456");
// 返回默认的 User
// return new org.springframework.security.core.userdetails.User(username,password, authorities);
// 返回自定义的 KiteUserDetails
User user = new User(username,password,authorities);
return user;
}
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
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.JwtAccessTokenConverter;
import javax.sql.DataSource;
import java.util.ArrayList;
/**
* Created by Administrator on 2020-06-10.
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
public PasswordEncoder passwordEncoder;
@Autowired
public UserDetailsService kiteUserDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
// @Autowired
// private TokenStore redisTokenStore;
// @Autowired
// private DataSource dataSource;
@Autowired
private TokenStore jwtTokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
//@Autowired
//private TokenEnhancer jwtTokenEnhancer;
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/**
* 普通 jwt 模式
*/
endpoints.tokenStore(jwtTokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.userDetailsService(kiteUserDetailsService)
/**
* 支持 password 模式
*/
.authenticationManager(authenticationManager);
/**
* jwt 增强模式
*/
// TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
// List enhancerList = new ArrayList<>();
// enhancerList.add(jwtTokenEnhancer);
// enhancerList.add(jwtAccessTokenConverter);
// enhancerChain.setTokenEnhancers(enhancerList);
// endpoints.tokenStore(jwtTokenStore)
// .userDetailsService(kiteUserDetailsService)
/**
* 支持 password 模式
*/
// .authenticationManager(authenticationManager)
// .tokenEnhancer(enhancerChain)
// .accessTokenConverter(jwtAccessTokenConverter);
/**
* redis token 方式
*/
// endpoints.authenticationManager(authenticationManager)
// .tokenStore(redisTokenStore)
// .userDetailsService(kiteUserDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//clients.jdbc(dataSource);
clients.inMemory()
.withClient("order-client")
.secret(passwordEncoder.encode("order-secret-8888"))
//.secret("order-secret-8888")
.authorizedGrantTypes("refresh_token", "authorization_code", "password")
.accessTokenValiditySeconds(3600)
.scopes("all")
.and()
.withClient("user-client")
.secret(passwordEncoder.encode("user-secret-8888"))
.authorizedGrantTypes("refresh_token", "authorization_code", "password")
.accessTokenValiditySeconds(3600)
.scopes("all")
.and()
.withClient("code-client")
.secret(passwordEncoder.encode("code-secret-8888"))
.authorizedGrantTypes("refresh_token","authorization_code")
.redirectUris("http://localhost:6102/client-authcode/login")
.accessTokenValiditySeconds(3600)
.scopes("all");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.checkTokenAccess("isAuthenticated()");
security.tokenKeyAccess("isAuthenticated()");
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Created by Administrator on 2020-06-10.
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 允许匿名访问所有接口 主要是 oauth 接口
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.authorizeRequests()
.antMatchers("/**").permitAll();
}
}
spring.application.name=auth-server
server.port=6001
### radm database
#spring.datasource.url=jdbc:sqlserver://localhost:1433;databasename=UI_MMS
#spring.datasource.username=sa
#spring.datasource.password=123
#spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
management.endpoint.health.enabled=true
eureka.instance.hostname=spring-cloud-ureka-server-one
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
com.example
demo
0.0.1-SNAPSHOT
demo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.boot
spring-boot-starter-actuator
org.projectlombok
lombok
org.springframework.cloud
spring-cloud-dependencies
Finchley.RELEASE
pom
import
org.springframework.boot
spring-boot-maven-plugin
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import okhttp3.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* Created by Administrator on 2020-06-15.
*/
@Controller
public class CodeClientController {
/**
* 用来展示index.html 模板
* @return
*/
@GetMapping(value = "index")
public String index(){
return "index";
}
@GetMapping(value = "login")
public Object login(String code,Model model) {
String tokenUrl = "http://localhost:6001/oauth/token";
OkHttpClient httpClient = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("grant_type", "authorization_code")
.add("client", "code-client")
.add("redirect_uri","http://localhost:6102/client-authcode/login")
.add("code", code)
.build();
Request request = new Request.Builder()
.url(tokenUrl)
.post(body)
.addHeader("Authorization", "Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==")
.build();
try {
Response response = httpClient.newCall(request).execute();
String result = response.body().string();
ObjectMapper objectMapper = new ObjectMapper();
Map tokenMap = objectMapper.readValue(result,Map.class);
String accessToken = tokenMap.get("access_token").toString();
Claims claims = Jwts.parser()
.setSigningKey("dev".getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(accessToken)
.getBody();
String userName = claims.get("user_name").toString();
model.addAttribute("username", userName);
model.addAttribute("accessToken", result);
return "index";
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@org.springframework.web.bind.annotation.ResponseBody
@GetMapping(value = "get")
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public Object get(Authentication authentication) {
//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getCredentials();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
String token = details.getTokenValue();
return token;
}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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 org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* Created by Administrator on 2020-06-13.
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("dev");
accessTokenConverter.setVerifierKey("dev");
return accessTokenConverter;
}
@Autowired
private TokenStore jwtTokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(jwtTokenStore);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll();
}
}
古时的风筝-OAuth2 Client
spring.application.name=client-authcode
server.port=6102
server.servlet.context-path=/client-authcode
spring.redis.database=2
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=100ms
security.oauth2.client.client-id=code-client
security.oauth2.client.client-secret=code-secret-8888
security.oauth2.client.user-authorization-uri=http://localhost:6001/oauth/authorize
security.oauth2.client.access-token-uri=http://localhost:6001/oauth/token
#security.oauth2.resource.id=user-client
#security.oauth2.resource.user-info-uri=user-info
security.oauth2.resource.jwt.key-uri=http://localhost:6001/oauth/token_key
security.oauth2.resource.jwt.key-value=dev
security.oauth2.authorization.check-token-access=http://localhost:6001/oauth/check_token
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
com.example
demo
0.0.1-SNAPSHOT
demo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.boot
spring-boot-starter-data-redis
io.jsonwebtoken
jjwt
0.9.1
com.squareup.okhttp3
okhttp
3.14.2
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.cloud
spring-cloud-dependencies
Finchley.RELEASE
pom
import
org.springframework.boot
spring-boot-maven-plugin
1.在浏览器中输入http://localhost:6001/oauth/authorize?client_id=code-client&response_type=code&redirect_uri=http://localhost:6102/client-authcode/login会出现以下页面,输入用户名admin 密码:123456(用户名密码是在程序中设定的)
2.输入用户名密码以后会跳转到授权页面,需要用户授权
3.点击授权以后,授权服务器会跳转到第三方应用,并传入code参数。第三方应用通过code参数获取token