spring cloud oauth2
- spring cloud oauth2简介
- 授权服务器
- 快速生成应用
- 资源服务器
- 客户端
spring cloud oauth2简介
文章主要贴了一部分关键代码,记录下这段时间研究的spring-cloud-oauth2,详细说明以及源码分析后续有时间补充.
授权服务器
授权服务器支持用户名密码登陆,短信验证登陆,企业微信企业钉钉用户静默登陆。
快速生成应用
1.通过spring官网 https://start.spring.io/ 可以快速构建项目。
2.在IDE中快速生成springboot项目,使用IntelliJ IDEA的同学可以可以利用IDEA快速构建。平时习惯使用eclipse的可以在spring官网下载工具http://spring.io/tools
3.关键配置
属性文件
#授权服务器管理的客户端配置在数据库中,所以这边配置了数据源
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.16.163:3306/sso_cas?useUnicode=yes&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: AAaa33
AuthorizationServerConfig授权服务器配置类
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
/**
* 客户端一些配置
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
/**
* 配置jwttokenStore
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
}
/**
* springSecurity 授权表达式,访问merryyou tokenkey时需要经过认证
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
//security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
/**
* JWTtokenStore
* @return
*/
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 生成JTW token
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("merryyou");
return converter;
}
}
WebSecurityConfig配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService ssoUserDetailsService;
@Autowired
private UserDetailsService phoneUserDetailService;
/**W
*
* <p>Title: passwordEncoder</p>
* <p>Description: 返回一个加密对象</p>
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();//由于我现在使用的版本默认加密对象变了,这边懒得重写解密的地方,还是把加密对象改为之前的。spring的版本变更会带来一些坑在里面。
}
@Autowired
@Qualifier("oauthAuthenticationDetailsSource")
private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
//.addFilterAt(getLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
http.addFilterBefore(getLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin().loginPage("/authentication/require")//自定义登录页
.loginProcessingUrl("/login2")
//.failureHandler(myAuthenctiationFailureHandler)由于自定义过滤器,此处设置无效
//.authenticationDetailsSource(authenticationDetailsSource)//此处无效,原因是在访问其他除登录外被替换,现改为直接在filter中注入。实现自定义的WebAuthenticationDetails,该类提供了获取用户登录时携带的额外信息的功能,默认实现WebAuthenticationDetails提供了remoteAddress与sessionId信息
.and().authorizeRequests()
.antMatchers("/authentication/require",
//"/login2",
//"/login",
"/exit",
"/authentication/appLogin",
"/weixinnotify",
"/authentication/formLogin",
"/authentication/validateDD",
"/authentication/captcha",
"/authentication/sendSMSCode",
"/dingtalk/auth",
"/dingtalkLogin",
"/**/*.js",
"/**/*.css",
"/**/*.jpg",
"/**/*.png",
"/**/*.woff2"
)
.permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
// http.formLogin().and().authorizeRequests().anyRequest().authenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//usernamePasswdAuthenticationProvider,phoneAuthenticationProvider实际未作验证,验证已在loginFilter中处理
auth.authenticationProvider(usernamePasswdAuthenticationProvider())
.authenticationProvider(phoneAuthenticationProvider())
.authenticationProvider(appAuthenticationProvider())
.authenticationProvider(wechatAuthenticationProvider())
.authenticationProvider(dingdingAuthenticationProvider())
;
}
/*@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider1 = new DaoAuthenticationProvider();
provider1.setUserDetailsService(userDetailsService);
// 设置userDetailsService
// 禁止隐藏用户未找到异常
provider1.setHideUserNotFoundExceptions(false);
// 使用BCrypt进行密码的hash
provider1.setPasswordEncoder(passwordEncoder());
return provider1;
}*/
@Bean
public UsernamePasswdAuthenticationProvider usernamePasswdAuthenticationProvider(){
UsernamePasswdAuthenticationProvider provider = new UsernamePasswdAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(ssoUserDetailsService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
return provider;
}
@Bean
public AppAuthenticationProvider appAuthenticationProvider() {
AppAuthenticationProvider provider = new AppAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(ssoUserDetailsService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
return provider;
}
@Bean
public WechatAuthenticationProvider wechatAuthenticationProvider() {
WechatAuthenticationProvider provider = new WechatAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(ssoUserDetailsService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
return provider;
}
@Bean
public DingdingAuthenticationProvider dingdingAuthenticationProvider() {
DingdingAuthenticationProvider provider = new DingdingAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(ssoUserDetailsService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
return provider;
}
@Bean
public PhoneAuthenticationProvider phoneAuthenticationProvider(){
PhoneAuthenticationProvider provider = new PhoneAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(phoneUserDetailService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
return provider;
}
@Bean
public LoginFilter getLoginFilter() {
LoginFilter filter = new LoginFilter();
try {
filter.setAuthenticationManager(this.authenticationManagerBean());
} catch (Exception e) {
e.printStackTrace();
}
filter.setAuthenticationSuccessHandler(new MyLoginAuthSuccessHandler());
//filter.setAuthenticationFailureHandler(myAuthenctiationFailureHandler);
filter.setAuthenticationDetailsSource(authenticationDetailsSource);
filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/authentication/require?error"));//此处改成登陆失败跳到登陆页请求,该请求是开放的,不会去重新saveRequest,如果重新在登陆成功之前saveRequest,会把保存的正确的从客户端过来的回掉地址冲洗掉,导致如果第一次登陆失败,第二次再登陆,即使登陆成功也会回不到客户端
return filter;
}
自定义登陆拦截器,此处集成了短信,企业微信以及企业钉钉的登陆,只贴了一部分关键的代码。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String type = obtainParameter(request, SPRING_SECURITY_RESTFUL_TYPE_KEY);
request.getParameter("smsPassWord");
AbstractAuthenticationToken authRequest;
String principal;
String credentials;
// 手机验证码登陆
if(SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(type)){
//登录之前先过滤(有自动注册的情况存在)
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_PHONE_KEY);
credentials = obtainParameter(request, SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY);
authRequest = new PhoneAuthenticationToken(principal, credentials);
setDetails(request, authRequest);
try {
userService.validPhoneLogin(authRequest);
}catch(Exception ex) {
throw new AuthenticationServiceException(ex.getMessage());
}
}
// 二维码扫码登陆
else if(SPRING_SECURITY_RESTFUL_TYPE_QR.equals(type)){
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_QR_CODE_KEY);
credentials = null;
authRequest = new QrAuthenticationToken(principal, credentials);
setDetails(request, authRequest);
}
//APP登陆
else if(SPRING_SECURITY_RESTFUL_TYPE_APP.equals(type)) {
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
credentials = null;
authRequest = new AppAuthenticationToken(principal, credentials);
setDetails(request, authRequest);
}
//微信企业号登陆
else if(SPRING_SECURITY_RESTFUL_TYPE_WECHAT.equals(type)) {
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
credentials = null;
authRequest = new WechatAuthenticationToken(principal, credentials);
setDetails(request, authRequest);
}
else if(SPRING_SECURITY_RESTFUL_TYPE_DINGDING.equals(type)) {
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
credentials = null;
authRequest = new DingdingAuthenticationToken(principal, credentials);
setDetails(request, authRequest);
}
// 账号密码登陆
else {
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
credentials = obtainParameter(request, SPRING_SECURITY_RESTFUL_PASSWORD_KEY);
authRequest = new UsernamePasswdAuthenticationToken(principal, credentials);
setDetails(request, authRequest);
//此处直接处理了
try {
userService.validUsernamePwdLogin(authRequest);
}catch(Exception ex) {
throw new AuthenticationServiceException(ex.getMessage());
}
}
return this.getAuthenticationManager().authenticate(authRequest);
}
资源服务器
- 属性配置文件
auth-server: http://127.0.0.1:10000 # sso-server地址
server:
port: 8085
servlet:
context-path: /resource
security:
oauth2:
client:
client-id: client1
client-secret: clientsecret1
resource:
#token-info-uri: ${auth-server}/oauth/check-token
jwt:
key-uri: ${auth-server}/oauth/token_key #解析jwt令牌所需要密钥的地址
- 资源服务器配置
@Configuration
@EnableResourceServer
public class SsoResourceServerConfig extends ResourceServerConfigurerAdapter {
/*@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/api/**").access("#oauth2.hasScope('write')");
}*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().exceptionHandling()
.authenticationEntryPoint(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and().authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("resource1");
}
- 怎么在资源服务器端刷新登陆后用户授权信息里面的相关数据(后续分析)
客户端
把生成的project加如下配置即可集成SSO,其他只要添加了对应的jar,还有启动类中需要添加注解@EnableOAuth2Sso
- 属性配置文件
auth-server: http://127.0.0.1:10000 # sso-server地址
server:
servlet:
context-path: /client1
port: 8083
security:
oauth2:
client:
client-id: client2
client-secret: clientsecret2
user-authorization-uri: ${auth-server}/oauth/authorize #请求认证的地址
access-token-uri: ${auth-server}/oauth/token #请求令牌的地址
resource:
#token-info-uri: ${auth-server}/oauth/check_token
user-info-uri: ${auth-server}/user
#jwt:
#key-uri: ${auth-server}/oauth/token_key #解析jwt令牌所需要密钥的地址