oauth2认证 php,Spring Boot 的 oAuth2 认证(附源码)

OAuth2 统一认证

原理

OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。

"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。

8de216782f432210ae846692894550a5.png

A、用户打开客户端以后,客户端要求用户给予授权。

B、用户同意给予客户端授权。

C、客户端使用上一步获得的授权,向认证服务器申请令牌。

D、认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

E、客户端使用令牌,向资源服务器申请获取资源。

F、资源服务器确认令牌无误,同意向客户端开放资源。

客户端授权模式

上面 B、流程中是用户给予客户端授权。oauth2 定义了下面四种授权方式:

授权码模式(authorization code)

简化模式(implicit)

密码模式(resource owner password credentials)

客户端模式(client credentials)

授权码模式

db3ec84e8291d36b6f40a4813a562f76.png

A、用户访问客户端,后者将前者导向认证服务器。

B、用户选择是否给予客户端授权。

C、假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

D、客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

E、认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

步骤说明:

① A步骤中,客户端申请认证的URI,包含以下参数:

* response_type:表示授权类型,必选项,此处的值固定为"code"

* client_id:表示客户端的ID,必选项

* redirect_uri:表示重定向URI,可选项

* scope:表示申请的权限范围,可选项

* state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz

&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1Host: server.example.com

② C步骤中,服务器回应客户端的URI,包含以下参数:

* code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。

* state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

HTTP/1.1 302 FoundLocation: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA

&state=xyz

③ D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

* grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。

* code:表示上一步获得的授权码,必选项。

* redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。

* client_id:表示客户端ID,必选项。

POST /token HTTP/1.1Host: server.example.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA

&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

④ E步骤中,认证服务器发送的HTTP回复,包含以下参数:

* access_token:表示访问令牌,必选项。

* token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。

* expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。

* refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。

* scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

HTTP/1.1 200 OK

Content-Type: application/json;charset=UTF-8

Cache-Control: no-store

Pragma: no-cache

{

"access_token":"2YotnFZFEjr1zCsicMWpAA",

"token_type":"example",

"expires_in":3600,

"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",

"example_parameter":"example_value"

}

密码模式

密码模式是将授权码模式中的授权码固定为用户名和密码。

6fe52d6a573eaa39b04a8a1555929600.png

A、用户向客户端提供用户名和密码。

B、客户端将用户名和密码发给认证服务器,向后者请求令牌。

C、认证服务器确认无误后,向客户端提供访问令牌。

步骤说明:

① B步骤中,客户端发出的HTTP请求,包含以下参数:

* grant_type:表示授权类型,此处的值固定为"password",必选项。

* username:表示用户名,必选项。

* password:表示用户的密码,必选项。

* scope:表示权限范围,可选项。

POST /token HTTP/1.1

Host: server.example.com

Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

② C步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。

HTTP/1.1 200 OK

Content-Type: application/json;charset=UTF-8

Cache-Control: no-store

Pragma: no-cache

{

"access_token":"2YotnFZFEjr1zCsicMWpAA",

"token_type":"example",

"expires_in":3600,

"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",

"example_parameter":"example_value"

}

注意,整个过程中,客户端不得保存用户的密码。

项目实践

1 .pom.xml

org.springframework.boot

spring-boot-starter-security

org.springframework.security.oauth

spring-security-oauth2

2 .SecurityConfig.java(主要配置文件)

package club.lemos.sso.config;

import club.lemos.sso.config.security.ClientResources;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.autoconfigure.security.SecurityProperties;

import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.boot.web.servlet.FilterRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.annotation.Order;

import org.springframework.security.authentication.dao.DaoAuthenticationProvider;

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.WebSecurityConfigurerAdapter;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.oauth2.client.OAuth2ClientContext;

import org.springframework.security.oauth2.client.OAuth2RestTemplate;

import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;

import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;

import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;

import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

import org.springframework.web.filter.CompositeFilter;

import javax.annotation.Resource;

import javax.servlet.Filter;

import java.util.ArrayList;

import java.util.List;

@Configuration

@EnableOAuth2Client

@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Resource

private OAuth2ClientContext oauth2ClientContext;

private final UserDetailsService userDetailService;

@Autowired

public SecurityConfig(UserDetailsService userDetailService) {

this.userDetailService = userDetailService;

}

/**

* 详细的路由配置参数

*

* @param http 配置

* @throws Exception 相关异常

*/

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.cors() // 跨域支持

.and()

.antMatcher("/**") // 捕捉所有路由

.authorizeRequests()

.antMatchers("/", "/login**", "/webjars/**", "/github").permitAll()

.anyRequest().authenticated()

.and()

.exceptionHandling()

.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) // 认证入口(跳转)

.and()

.formLogin().loginProcessingUrl("/doLogin") // 表单请求的路由为 "POST /login"

.defaultSuccessUrl("/").failureUrl("/login?err=1")

.permitAll()

.and()

.logout().logoutUrl("/logout") // 注销请求的路由为 "GET /logout"

.logoutSuccessUrl("/")

.permitAll()

.invalidateHttpSession(true)

.clearAuthentication(true)

.and()

.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // csrf 安全处理

.and()

.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class); // 第三方授权层

}

@Bean

public FilterRegistrationBean oauth2ClientFilterRegistration(

OAuth2ClientContextFilter filter) {

FilterRegistrationBean registration = new FilterRegistrationBean();

registration.setFilter(filter);

registration.setOrder(-100);

return registration;

}

private Filter ssoFilter() {

CompositeFilter filter = new CompositeFilter();

List filters = new ArrayList<>();

filters.add(ssoFilter(github(), "/login/github"));

filter.setFilters(filters);

return filter;

}

private Filter ssoFilter(ClientResources client, String path) {

OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);

OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);

filter.setRestTemplate(template);

UserInfoTokenServices tokenServices = new UserInfoTokenServices(

client.getResource().getUserInfoUri(), client.getClient().getClientId());

tokenServices.setRestTemplate(template);

filter.setTokenServices(tokenServices);

return filter;

}

/**

* github 授权连接

*

* @return 第三方授权连接对象

*/

@Bean

@ConfigurationProperties("github")

public ClientResources github() {

return new ClientResources();

}

/**

* BCrypt 密码加密

*

* @return BCrypt 编码器

*/

@Bean

public PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

@Bean

public DaoAuthenticationProvider authProvider() {

DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

authProvider.setUserDetailsService(userDetailService);

// authProvider.setPasswordEncoder(passwordEncoder());

return authProvider;

}

/**

* 用户认证服务(用户名+密码)

*

* @param auth 认证

* @throws Exception 相关异常

*/

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.authenticationProvider(authProvider());

}

// TODO 提供一个访问用户信息(昵称,角色信息等等)的 api

// TODO 提供 cookie持久化时间

// TODO 注销功能实现

}

ClientResources.java

package club.lemos.sso.config.security;

import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;

import org.springframework.boot.context.properties.NestedConfigurationProperty;

import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;

public class ClientResources {

@NestedConfigurationProperty

private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();

@NestedConfigurationProperty

private ResourceServerProperties resource = new ResourceServerProperties();

public AuthorizationCodeResourceDetails getClient() {

return client;

}

public ResourceServerProperties getResource() {

return resource;

}

}

3 .application.yml(配置文件)

logging:

level:

org:

springframework:

security: DEBUG

root: INFO

server:

port: 8080

spring:

datasource:

dbcp2:

initial-size: 10

max-idle: 8

min-idle: 8

driverClassName: com.mysql.jdbc.Driver

password: root

url: jdbc:mysql://localhost:3306/sso?useSSL=false

username: root

freemarker:

charset: UTF-8

check-template-location: true

content-type: text/html

expose-request-attributes: true

expose-session-attributes: true

request-context-attribute: request

thymeleaf:

cache: false

prefix: classpath:/templates/

suffix: .html

github:

client:

clientId: dd2bf79a9e6be256f0e8

clientSecret: 0e555a2ee5d627e3abdee3f5096de6d8278d3413

accessTokenUri: https://github.com/login/oauth/access_token

userAuthorizationUri: https://github.com/login/oauth/authorize

clientAuthenticationScheme: form

resource:

userInfoUri: https://api.github.com/user

4 .UserDetailsServiceImpl.java(用户接口实现)

package club.lemos.sso.config.security;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

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.Arrays;

import java.util.Date;

@Service

public class UserDetailsServiceImpl implements UserDetailsService {

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

// TODO 从数据库中查找用户

return new UserDetailsImpl(1L, "lisi", "password",

Arrays.asList(new SimpleGrantedAuthority("USER"), new SimpleGrantedAuthority("ADMIN")), true, new Date());

}

}

UserDetailsImpl.java

package club.lemos.sso.config.security;

import com.fasterxml.jackson.annotation.JsonIgnore;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

import java.util.Date;

public class UserDetailsImpl implements UserDetails {

private final Long id;

private final String username;

private final String password;

private final Collection extends GrantedAuthority> authorities;

private final boolean enabled;

private final Date lastPasswordResetDate;

public UserDetailsImpl(Long id, String username, String password, Collection extends GrantedAuthority> authorities, boolean enabled, Date lastPasswordResetDate) {

this.id = id;

this.username = username;

this.password = password;

this.authorities = authorities;

this.enabled = enabled;

this.lastPasswordResetDate = lastPasswordResetDate;

}

@JsonIgnore

public Long getId() {

return id;

}

@Override

public String getUsername() {

return username;

}

@JsonIgnore

@Override

public boolean isAccountNonExpired() {

return true;

}

@JsonIgnore

@Override

public boolean isAccountNonLocked() {

return true;

}

@JsonIgnore

@Override

public boolean isCredentialsNonExpired() {

return true;

}

@JsonIgnore

@Override

public String getPassword() {

return password;

}

@Override

public Collection extends GrantedAuthority> getAuthorities() {

return authorities;

}

@Override

public boolean isEnabled() {

return enabled;

}

@JsonIgnore

public Date getLastPasswordResetDate() {

return lastPasswordResetDate;

}

}

相关问题

问题一、提供oAuth2 接口 及 token 的获取

之前的Web端(B\S结构),可以正常通信。登录跳转什么的。对于受限资源,需要通过 oauth2授权( C\S 结构)。可以通过Web端发送一个请求进行认证。认证成功,获得 token,可以使用 token访问f服务器受限资源。formLogin 即表单登录,比较好理解。oauth2 登录,需要先获得 token,再用它访问 api 资源服务器,获取信息。

1. 证书 token

curl client:secret@localhost:8090/oauth/token -d grant_type=client_credentials

0b69bf3fb151bcaeae5a1e2fc8e40135.png

2. 密码 token

当应用启动时,springboot 会创建一个默认的用户,用户id为‘user‘。密码是随机的,但可以从打印的日志中看到。

curl client:secret@localhost:8090/oauth/token -d grant_type=password -d username=user -d password=...

或者使用定义好的用户名及密码进行认证

curl client:secret@localhost:8090/oauth/token -d grant_type=password -d username=admin -d password=admin

a003a4e8fcc93a5ed4117e5e974f2da0.png

认证后,访问资源服务

curl http://localhost:8090/api/users -H "Authorization: bearer 7e7b7ced-3747-43a2-8134-c7e6b87c6451"

3. 页面中,可以通过发送带 auth 认证头的请求,访问oauth服务器

ajax 请求认证:

$.ajax({

type: "GET",

url: "index1.php",

dataType: 'json',

async: false,

headers: {

"Authorization": "Basic " + btoa(USERNAME + ":" + PASSWORD)

},

data: '{ "comment" }',

success: function (){

alert('Thanks for your comment!');

}});

3314d4e99d4b43118c846d8761fc5e4c.png

从 github上申请 开发权限:

50b6bf327742e39d53976b71b32faf75.png

执行流程

1> 点击页面的 callback超链接(比如 go to github)。

callbackURL = http://localhost:8090/login/github  GET

2> 重定向到授权页面,用户点击授权

user-authorization-uri = https://github.com/login/oauth/authorize?client_id=141c0a61de83cf2d9841&redirect_uri=http://localhost:8090/login/github&response_type=code&scope=user&state=4jgVT2

e47fe24529919065443f6167b04d944b.png

3>  重定向回自己的页面,并携带一个 code (授权码) 和 前一步中的 state参数,如果 states 匹配,则可以发送一个 POST https://github.com/login/oauth/access_token

http://localhost:8090/login/github?code=2c6dcdce82ef1473e148&state=4jgVT2

4> 请求 token(授权成功,会自动发送这个请求)

https://github.com/login/oauth/access_token    POST

响应 token(包含着授权信息,存储在 JSESSION 中)

access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer

或者

Accept: application/json {"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"}

5> 访问 Github API(使用 js访问)

GET https://api.github.com/user?access_token=...

或者设置头信息

Authorization: token OAUTH-TOKEN

问题三、 github 的token的获取

使用 RestTemplate访问。

OAuth2RestTemplate template = oAuth2RestTemplate(new AuthorizationCodeResourceDetails());

template.setRetryBadAccessTokens(false);

token = template.getAccessToken();

或者在配置文件中从上下文中直接获取。

OAuth2AccessToken accessToken = oauth2ClientContext.getAccessToken();

完整项目下载

完整项目下载—— 点我

其他

参阅文档

springboot 官方文档

Spring-Boot-Reference-Guide

https://qbgbook.gitbooks.io/spring-boot-reference-guide-zh/content/

Spring Boot and OAuth2 *****接入github 的详细配置******

https://spring.io/guides/tutorials/spring-boot-oauth2/

---------------------------------------------------------------------------

有用的文章

spring security & oauth2

http://www.jianshu.com/p/6b211e845b16/

spring-security-oauth2 server

http://www.jianshu.com/p/028043425b09

详解Spring Security进阶身份认证之UserDetailsService(附源码)

http://favccxx.blog.51cto.com/2890523/1609692

---------------------------------------------------------------------------

github 相关文档

https://developer.github.com/v3/

https://developer.github.com/v3/oauth/

https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations

https://help.github.com/articles/connecting-with-third-party-applications/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot提供了Spring Security来实现OAuth2认证,下面是Spring Boot OAuth2认证过程的简要流程: 1. 用户访问客户端,客户端需要获取用户授权,重定向到授权服务器的授权页面; 2. 用户在授权页面输入账号密码并授权,授权服务器验证用户身份并返回授权码; 3. 客户端使用授权码向授权服务器请求访问令牌; 4. 授权服务器验证授权码并颁发访问令牌; 5. 客户端使用访问令牌向资源服务器请求数据。 下面是Spring Boot OAuth2认证源码实现: 1. 导入OAuth2依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.1.0.RELEASE</version> </dependency> ``` 2. 配置授权服务器 ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Autowired private PasswordEncoder passwordEncoder; @Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource) .withClient("client") .secret(passwordEncoder.encode("secret")) .authorizedGrantTypes("password", "refresh_token") .scopes("read", "write") .accessTokenValiditySeconds(1800) .refreshTokenValiditySeconds(3600); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } } ``` 3. 配置资源服务器 ```java @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/**").authenticated() .and().csrf().disable(); } } ``` 4. 配置安全认证 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and().formLogin().permitAll(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 以上是Spring Boot OAuth2认证的简要流程和源码实现,具体的实现细节可以参考Spring官方文档和源码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值