http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
1、什么是OAuth
OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用。目前的版本是2.0版。
OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动
OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。“客户端"不能直接登录"服务提供商”,只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。
"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。
2、OAuth2的四种模式:
a、授权码模式(别人来调用我们的服务)
b、简化模式(pc模式,跳过了"授权码"这个步骤)
c、密码模式(自己的服务)
d、客户端模式(服务与服务之间的调用)
一、springcloud + OAuth2
1方案:
添加pom依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
认证服务配置:
import com.gao.handler.MyAuthorationFaileHandler;
import com.gao.handler.MyAuthorationSeccuserHandler;
import com.gao.handler.MyAuthorictionDeniedHandler;
import com.gao.service.MyUserDetailService;
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.configuration.EnableGlobalAuthentication;
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.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
@Configuration
@EnableWebSecurity
@EnableGlobalAuthentication //开启全局的注解授权(使用注解授权)
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置认证管理器,授权模式为“poassword”时会用到
*/
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public PersistentTokenRepository getTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//第一次启动的时候创建表
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
//加密配置
@Bean
public PasswordEncoder getPwdEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//查询所有的权限
http.exceptionHandling().accessDeniedHandler((new MyAuthorictionDeniedHandler()))//没有权限时返回的信息 .and().authorizeRequests().antMatchers("/login").permitAll()//匹配所有,对登录请求全部放行
// .antMatchers("/login.html").permitAll()//自定义登录页面放行
.anyRequest().authenticated()//比对请求,进行权限验证
.and().formLogin()//允许表单登录
// .successForwardUrl("/succeed")//登录成功后的跳转请路径
.successHandler(new MyAuthorationSeccuserHandler())
.failureHandler(new MyAuthorationFaileHandler())
// .loginPage("/login.html")//登录页面
// .loginProcessingUrl("/login")
.and().logout().permitAll()//允许所有的退出操作
.and().csrf().disable(); //关闭跨站请求
//记住我设置
http.rememberMe()
.tokenRepository(getTokenRepository())
.userDetailsService(new MyUserDetailService())
.tokenValiditySeconds(3600);
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.GrantedAuthority;
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.provisioning.InMemoryUserDetailsManager;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//根据传入的用户名去数据库查询用户
//判断是否为空,
//空,抛出异常
//非空,返回用户
List<GrantedAuthority> authorities = new ArrayList<>();
//查询权限
List<Object> userPremis = new ArrayList<>();
userPremis.forEach(t->{
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("权限值");
authorities.add(simpleGrantedAuthority);
});
// public User(String username, String password, Collection<? extends GrantedAuthority > authorities) {
// this(username, password, true, true, true, true, authorities);
// }
return new User("username","password",authorities);
}
}
授权服务配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer //开启授权服务配置,声明该服务作为授权服务方
public class MyAuthSecurityConfig extends AuthorizationServerConfigurerAdapter {
//授权服务安全配置:配置哪些路径放行(检查token的路径要放行)
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("permitAll()") //对应/oauth/check_token ,路径公开
.allowFormAuthenticationForClients(); //允许客户端进行表单身份验证,使用表单认证申请令牌
}
@Resource
private DataSource dataSource;
@Resource
private BCryptPasswordEncoder bCryptPasswordEncoder;
/*客户端详情:配置客户端请求的参数
ClientDetailsServiceConfigurer 是这对于客户端详情的配置,我们通过withClientDetails关联一个JdbcClientDetailsService,默认会去找数据库中的名字为 oauth_client_details 表中的数据作为客户端详情的配置*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(getDetailsService());
}
public ClientDetailsService getDetailsService(){
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource); jdbcClientDetailsService.setPasswordEncoder(bCryptPasswordEncoder);
return jdbcClientDetailsService;
}
@Resource
private AuthenticationManager authenticationManager;
//授权服务端点:配置授权码和令牌的管理/存储方式
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.authorizationCodeServices(getCodeServices()); //授权码的管理服务 ,默认读取 oauth_code表
endpoints.tokenServices(getTokenServices()); //令牌的管理服务
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
}
@Bean
public AuthorizationServerTokenServices getTokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(getDetailsService()); //指定客户端详情配置
services.setSupportRefreshToken(true); //支持产生刷新token
services.setTokenStore(declareTokenStore()); //token存储方式
return services;
}
//基于内存的Token存储
public TokenStore declareTokenStore() {
return new InMemoryTokenStore();
}
@Bean
public AuthorizationCodeServices getCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
}
1、ClientDetailsServiceConfigurer :用来配置客户端详情服务:如配置客户端id(client_id)资源id、客户端密钥(secrect)、授权方式、scope等,可以基于内存或jdbc。(可以理解为是对浏览器向授权服务器获取授权码或令牌时需要提交的参数配置)
2、AuthorizationServerEndpointsConfigurer:配置令牌的访问端点url和令牌服务,如配置如何管理授权码(内存或jdbc),如何管理令牌(存储方式,有效时间等等)
3、AuthorizationServerSecurityConfigurer: 用来配置令牌端点的安全约束,如配置对获取授权码,检查token等某些路径进行放行
创建客户端详情配置表SQL
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`
创建授权码SQL
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(255) DEFAULT NULL COMMENT '授权码(未加密)',
`authentication` varbinary(5000) DEFAULT NULL COMMENT 'AuthorizationRequestHolder.java对象序列化后的二进制数据'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
授权测试:
第一步,通过浏览器获取授权码,GET访问:
http://localhost:8100/oauth/authorize?client_id=webapp2&response_type=code&redirect_uri=http://www.baidu.com
第二步,使用Postmain获取令牌,Post访问
http://localhost:8100/oauth/token?client_id=webapp2&client_secret=123&grant_type=authorization_code&code=gD8ZbB&redirect_uri=http://www.baidu.com
刷新token:
http://localhost:8100/oauth/token?grant_type=refresh_token&refresh_token=刷新Token值&client_id=webapp&client_secret=secret
资源方配置
在资源服务中我们需要解决的问题是,当请求进来,请求是需要携带token,我们需要配置资源服务如何对token进行校验和授权
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
@Configuration
@EnableResourceServer //作为资源服务方
public class MyRresourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("courseId");
resources.tokenServices(getResourceTokenServices());
}
@Bean
public ResourceServerTokenServices getResourceTokenServices() {
//使用远程服务请求授权服务器校验token , 即:资源服务和授权服务器不在一个主机
RemoteTokenServices services = new RemoteTokenServices();
//授权服务地址 , 当浏览器访问某个资源时就会调用该远程授权服务地址去校验token
//要求请求中必须携带token
services.setCheckTokenEndpointUrl("http://localhost:8100/oauth/check_token");
//客户端id,对应认证服务的客户端详情配置的clientId
services.setClientId("webapp");
//密钥,对应认证服务的客户端详情配置的clientId
services.setClientSecret("123");
return services;
}
/**
* http安全配置
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//校验scope必须为all , 对应认证服务的客户端详情配置的clientId
.antMatchers("/**").access("#oauth2.hasScope('all')")
//关闭跨域伪造检查
.and().csrf().disable()
//把session设置为无状态,意思是使用了token,那么session不再做数据的记录
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
单点登录:
sso single sign on
在一个站点登录,其他站点在一定时间内都不用重现登录
共享token
Cookie:
cookie可以跨站。cookie不可跨域调用。每次请求一个新的页面的时候cookie都会被发送过去
sessionstorage
sessionStorage是在同源的窗口中,始终存在数据。也就是说,这个浏览器没有关闭,即使刷新页面或进入到同源另一个页面,数据仍然存在,关闭后,sessionstorage就会被销毁。
localstorage
无感刷新:
如果用户还在操作,此时token过期了,应该系统自动获取新token
1、如果token过期了,访问后台报错,报错了如果是过期的异常,全局异常中捕获该异常,捕获以后重新获取新的token 返回给前端。
2、在过期时间还有5分钟的时候,前端主动发送一个请求
3.1、在离过期时间还有5分钟之内自动刷新token
3.2、最后一次操作在过期时间之后,但是离上一次操作的时间差值在30分钟之内
outTime :过期时间
curTime :当前时间
lastTime : 最后一次操作时间
过期前5分钟内自动刷新
if(outTime - curTime < 5 && outTime - curTime > 0){
//刷新token
}
$.cookie("lastTime",lastTime)
if(lastTime > outTime && cutTime > expTime && curTime - lastTime <=30){
//刷新token
}
$.token('lastTime',curTime)
【注意】当配置域名之后,在访问页面的时候使用域名进行访问。避免无法使用cookie