springcloud oauth 官方页面 https://spring.io/projects/spring-security-oauth#learn
oauth2官网 https://oauth.net/2/
OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.
OAuth 2.0是用于授权的行业标准协议。 OAuth 2.0着眼于简化客户端开发人员,同时为Web应用程序,桌面应用程序,移动电话和客厅设备提供特定的授权流程。 该规范及其扩展名正在IETF OAuth工作组内开发。
首先来了解下Oatuh2中的几个名字,方便下文的阐述。
Oauth2的作用就是让第三方应用在用户(资源持有者)授权的情况下,通过认证服务器的认证,从而安全的在资源服务器上获得对应的用户资源的流程指导。
oauth2常用的四种模式
优点:不需要多次请求转发,额外开销,同时可以获取更多的用户信息。(都拿到账号密码了)
缺点:局限性,认证服务器和应用方必须有超高的信赖
应用场景:自家公司搭建的认证服务器
这是一种最简单的模式,只要client请求,我们就将AccessToken发送给它。这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。
因此这种模式一般用来提供给我们完全信任的服务器端服务。在这个过程中不需要用户的参与。
授权码默认简化版本
理论到此结束
这里创建3个服务
第一个 eureka 注册中心
第二个 auth-service 认证服务
第三个 resource-service 资源服务器
使用的springcloud 版本 为 Hoxton.SR4
auth-service服务开始
认证服务整体如图
pom依赖配置
org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-oauth2 org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web
AuthorizationServerConfig
/** * 认证配置 * * @author admin * @date 2020-06-17 17:06:30 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private final AuthenticationManager authenticationManager; private final ClientDetailsService clientDetailsService; private final UserDetailsService userDetailsService; @Autowired public AuthorizationServerConfig(ClientDetailsService clientDetailsService, AuthenticationManager authenticationManager, UserDetailsService userDetailsService) { this.clientDetailsService = clientDetailsService; this.authenticationManager = authenticationManager; this.userDetailsService = userDetailsService; } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //用来配置令牌端点(Token Endpoint)的安全与权限访问。 security .tokenKeyAccess("permitAll()") .checkTokenAccess("permitAll()") .accessDeniedHandler(new AccessDeniedHandlerImpl()) //允许表单传入 client_id client_secret进行认证 .allowFormAuthenticationForClients() ; } /** * 认证配置 * @param endpoints endpoints * @throws Exception endpoints */ @Override public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //用来配置授权以及令牌(Token)的访问端点和令牌服务(比如:配置令牌的签名与存储方式) endpoints .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) .authenticationManager(this.authenticationManager) //token 存储 .tokenStore(tokenStore()) .userDetailsService(userDetailsService) ; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //内存中配置客户端信息 //用来配置客户端详情信息,一般使用数据库来存储或读取应用配置的详情信息 clients.inMemory().withClient("user") .accessTokenValiditySeconds(30 * 60) .refreshTokenValiditySeconds(30 * 60) .authorizedGrantTypes("authorization_code", "refresh_token", "client_credentials", "password","implicit") .scopes("all", "read", "write") // // secret密码配置从 Spring Security 5.0开始必须以 {加密方式}+加密后的密码 这种格式填写 // /* // * 当前版本5新增支持加密方式: // * bcrypt - BCryptPasswordEncoder (Also used for encoding) // * ldap - LdapShaPasswordEncoder // * MD4 - Md4PasswordEncoder // * MD5 - new MessageDigestPasswordEncoder("MD5") // * noop - NoOpPasswordEncoder // * pbkdf2 - Pbkdf2PasswordEncoder // * scrypt - SCryptPasswordEncoder // * SHA-1 - new MessageDigestPasswordEncoder("SHA-1") // * SHA-256 - new MessageDigestPasswordEncoder("SHA-256") // * sha256 - StandardPasswordEncoder // */ .secret("{noop}style") .redirectUris("https://www.baidu.com") ; } /** * Persistence interface for OAuth2 tokens. * * @return TokenStore */ @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } }
SecurityConfig
package com.style.auth.config; 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.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** * Security 配置 * * @author admin * @date 2020-06-18 15:23:04 */ @EnableWebSecurity @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated() .and() //basic 弹窗登录 //.httpBasic() //表单登录 .formLogin() .and() .csrf().disable() ; } @Bean public UserDetailsService userDetails() { UserDetails admin = User.withUsername("admin") //,默认BCryptPasswordEncoder 更多实现 org.springframework.security.crypto.password.PasswordEncoder //可查看该接口的实现 // password Spring Security 5.0开始必须以 {加密方式}+加密后的密码 这种格式填写 .password("{bcrypt}$2a$10$ZlFDDZMkZ9P7Yb4BsZ50ZueNzn7yM3GTJD97M5cJMWDu4oKr1Lsuq") .roles("ADMIN", "USER") .build(); UserDetails user = User.withUsername("user") .password("{bcrypt}" + new BCryptPasswordEncoder().encode("123456")) .roles("USER") .build(); //内存用户管理器 InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(); userDetailsManager.createUser(admin); userDetailsManager.createUser(user); return userDetailsManager; } @Bean @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } } AuthServerApplication 启动类 /** * 认证服务 * * @author admin */ @SpringBootApplication @EnableDiscoveryClient public class AuthServerApplication { public static void main(String[] args) { SpringApplication.run(AuthServerApplication.class, args); } }
resource-service
整体目录结构
pom依赖配置
org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-oauth2 org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web
ResourceServerConfig
package com.style.auth.config; 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.RemoteTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; /** * 资源服务配置 * * @author admin * @date 2020-06-18 16:59:28 */ @EnableResourceServer @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ID = "resource"; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId(RESOURCE_ID); resources.tokenServices(tokenService()); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth/**").permitAll() .anyRequest() .authenticated() .and() .csrf() .disable() .formLogin() ; } @Bean public RemoteTokenServices tokenService() { //远程token服务 即为认证的服务器的check_token地址 RemoteTokenServices remoteTokenServices = new RemoteTokenServices(); remoteTokenServices.setClientId("user"); remoteTokenServices.setClientSecret("style"); // 20110 端口为auth-service 的端口 remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:20110/oauth/check_token"); return remoteTokenServices; } /** * Persistence interface for OAuth2 tokens. * * @return TokenStore */ @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } }
SecurityConfig
package com.style.auth.config; import org.springframework.context.annotation.Configuration; 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; /** * Security配置 * * @author admin * @date 2020-06-18 15:23:04 */ @EnableWebSecurity @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated() .and() //表单登录 .formLogin() ; } }
ResourceController
@RestController public class ResourceController { @Value("${server.port}") private Integer serverPort; @GetMapping("/port") public String getPort() { return "server port is :" + serverPort; } }
ResourceServerApplication 启动类
启动 eureka
启动 auth-service
启动 resource-service
四个模式按个测试下
1.密码模式
postman 访问
http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=password&username=user&password=123456
2.客户端凭证模式
http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=client_credentials&scope=read
3.授权码模式
浏览器请求
获取code
http://localhost:20110/oauth/authorize?response_type=code&redirect_uri=https://www.baidu.com&client_id=user&scop=all
跳转登录界面
输入账号密码进行登录
重定向 到 https://www.baidu.com/?code=Zs97m8 并且携带了code码
下一步根据 code 进行获取token
请求地址
http://localhost:20110/oauth/token?code=Zs97m8&grant_type=authorization_code&redirect_uri=https://www.baidu.com&scope=all&client_id=user&client_secret=style
4.简化模式
http://localhost:20110/oauth/authorize?response_type=token&client_id=user&redirect_uri=https://www.baidu.com
首先重定向到登录页面
登录成功 重定向到设置的重定向 地址中
改地址已经存在了 access_token
https://www.baidu.com/#access_token=eb96d104-cec7-4d2e-9da6-6fd8f2851446&token_type=bearer&expires_in=1031&scope=all%20read%20write
刷新token 接口
http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=refresh_token&refresh_token=819e651a-b704-4bce-9147-532c5fc06ada
验证下 resource_serivice
该接口为resource_service中定义的一个接口 位于ResourceController类中
第一次访问没有携带token 进行访问 提示错误
第二次
亦或者
请求头里添加 Authorization value为 bearer{空格}+token
源码见 https://github.com/passliang/panda
参考知乎文章https://zhuanlan.zhihu.com/p/84670338
https://www.cnblogs.com/Innocent-of-Dabber/p/11009811.html