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

  1. 属性配置文件
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令牌所需要密钥的地址
       
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值