OAuth2是用来写让第三方应用在不接触账号密码的情况下完成登录功能的框架
授权码模式
首先会让客户端跳转到登录页面比如京东的微信登录 点击微信登录之后会访问微信提供的授权服务器 会先返回一个二维码让我们扫 扫了之后并且在手机上点击同意登录 会访问微信的授权服务器给京东返回一个授权码 然后京东后台 会根据授权码 有了授权码就可以得到一个access_token 然后就可以根据access_token获取用户信息 用微信开放文档上写的举例
网站应用微信登录开发指南:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
首先获取微信的授权码专业单词叫做code 根据微信文档说的会跳转到
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
这个链接 这个链接就会显示微信登录的二维码 当我们扫码并且登录之后 就会返回一个code
然后通过https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code这个链接根据code获取access_token得到access_token之后就可以获取微信用户信息了 这也就是OAuth2的一个流程 OAuth2是一个第三方账号登录标准微信、微博等都是遵循这个标准下面看代码实现
授权码模式
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
或者 引入spring cloud oauth2依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- spring cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置spring security
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll()
.and().authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest().authenticated()
.and().logout().permitAll()
.and().csrf().disable();
}
}
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123456");
return new User("fox",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication) {
return authentication.getPrincipal();
}
}
配置授权服务器
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client_id
.withClient("client")
//配置client-secret
.secret(passwordEncoder.encode("123123"))
//配置访问token的有效期
.accessTokenValiditySeconds(3600)
//配置刷新token的有效期
.refreshTokenValiditySeconds(864000)
//配置redirect_uri,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
//配置申请的权限范围
.scopes("all")
//配置grant_type,表示授权类型
.authorizedGrantTypes("authorization_code");
}
}
测试
获取授权码
http://localhost:8080/oauth/authorize?response_type=code&client_id=client
或者
http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
如果使用http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
登录之后浏览器的网址框哪里会显示baidu.com/?code=这里就是随机的code
然后再访问localhost:8080/oauth/token这个/oauth/token路径访问时带上grant_tyoe=authorization_code,code=之前返回的code,redirect_uri=http://www.baidu.com这三个参数就可以返回access_token 然后调用/user/getCurrentUser接口时带上access_token也就是这样/user/getCurrentUser?access_token=这里写返回的哪个数据 以上调用操作都是在后端实现 前端无法看到 很安全
密码模式
修改WebSecurityConfig,增加AuthenticationManager
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll()
.and().authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest().authenticated()
.and().logout().permitAll()
.and().csrf().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
修改AuthorizationServerConfig配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig2 extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManagerBean;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单认证
security.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/**
*授权码模式
*http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
*http://localhost:8080/oauth/authorize?response_type=code&client_id=client
*
* password模式
* http://localhost:8080/oauth/token?username=fox&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all
*
* 客户端模式
* http://localhost:8080/oauth/token?grant_type=client_credentials&scope=all&client_id=client&client_secret=123123
*/
clients.inMemory()
//配置client_id
.withClient("client")
//配置client-secret
.secret(passwordEncoder.encode("123123"))
//配置访问token的有效期
.accessTokenValiditySeconds(3600)
//配置刷新token的有效期
.refreshTokenValiditySeconds(864000)
//配置redirect_uri,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
//配置申请的权限范围
.scopes("all")
/**
* 配置grant_type,表示授权类型
* authorization_code: 授权码
* password: 密码
* client_credentials: 客户端
*/
.authorizedGrantTypes("authorization_code","password","client_credentials");
}
}
通过浏览器测试,需要配置支持get请求和表单验证
http://localhost:8080/oauth/token?username=fox&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all
这样就可以直接获取access_token不需要先获取code然后才能得到access_token
token存储到redis
加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
修改application.yaml
spring:
redis:
host: 127.0.0.1
database: 0
编写redis配置类
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore tokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
}
在授权服务器配置中指定令牌的存储策略为Redis
@Autowired
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
.tokenStore(tokenStore) //指定token存储到redis
.reuseRefreshTokens(false) //refresh_token是否重复使用
.userDetailsService(userService) //刷新令牌授权包含对用户信息的检查
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}