3. spring security & oauth2

上一次写到使用spring-security做简单登录应用,先补交家庭作业

如何自定义登录页面#####

  1. 修改WebSecurityConfig
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
                .and()
                .formLogin().loginPage("/login").loginProcessingUrl("/login.do").defaultSuccessUrl("/user/info")
                .failureUrl("/login?err=1")
                .permitAll()
                .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .permitAll()
        ;

    }

解释看代码字面意思就懂了,没什么特殊的,还可以修改比如登录表单里的用户名和密码的名字,还可以添加各种登录成功之后的handler等等,写法都一样。

  1. 添加LoginController
@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login(String err, ModelMap modelMap) {
        if (StringUtils.hasLength(err)) {
            modelMap.put("err", err);
        }
        return "login";
    }
}
  1. 添加页面就是一个普通的form表单
用户名或密码错误

这里需要注意的就是表单里有一个隐藏的input叫_csrf是用来防范跨域攻击的,详情查看CSRF。可以在配置类关掉.csrf().disable(),当然是不推荐的。
以上代码地址:v1.0

oauth2#####

上面的代码应该满足一般意义上的网站登录,下面简单介绍怎么使用spring-security-oauth这东西比较复杂,分两部分,先讲一部分。

至于OAuth是什么东西,请问百度或谷歌,官方地址在https://oauth.net/2/
OAuth简单来说是一种协议。在我看来就是对上篇文章所说的登录鉴权模式的一种补充(当然不是严格意义的说,我只是想简单明了的阐述一下这是一个什么东西),大家都知道以前系统是分为两种模式的——B/SC/S,上篇文章讲述的都是B/S模式下的登录授权方式,但是对于C端即Client是不好使的,所以就有了OAuth协议(来历纯属虚构),那么什么情况下会使用呢?——虽然感觉很神秘,但是这个协议应用却很常见——几乎所有的大公司都在使用,比如微信登录微博登录GitHub登录Google登录QQ登录FaceBook登录Twitter登录等等这种第三方登录都是使用的这个协议,可以看看微博登录的地址即可:

https://api.weibo.com/oauth2/authorize?scope=email&state=f4dde19fcea5b2ab8c3e682da17a511d&redirect_uri=http%3A%2F%2Fwww.zhihu.com%2Foauth%2Fcallback%2Fsina&response_type=code&client_id=3063806388
oauth2协议流程######

在网上盗的别人的一个图:

3. spring security & oauth2_第1张图片
OAuth2 认证
  1. Client向服务器发起OAuth请求交换authorization_code,需要携带的参数:
    • client_id:可以理解为客户端用于登录的用户名
    • response_type:本次请求什么?一般首次请求为code
    • redirect_uri:认证成功返回的地址
    • scope:权限范围,指本次授权获取资源的权限范围,比如只读,可读写之类
    • state:一般为随机数,可选,服务器会原样返回,用于客户端验证服务器
      这些参数名称以及值不是严格意义不变的,各个认证服务器都会有自己的风格。
  2. 如果是浏览器发起的第三方登录,比如上述举例的在知乎上使用微博登录,输入微博的用户名和密码,验证通过之后,则服务器会自动从微博重定向到刚才的redirect_uri,严谨一点的服务器还会询问你是否允许比如知乎请求你的微博个人信息。如果不是浏览器比如手机APP,服务器会直接返回code
  3. 使用code获取access_token,这一步一般是在客户端的服务器(不是第三方认证服务器,比如上文的知乎浏览器即客户端,知乎的服务器即客户端的服务器,微博即第三方认证服务器)进行的,就是一般来说用户是无感知的(不是所有的认证服务器都有这一步,只是更加安全而已,服务器实现方式不同,有的是直接可以使用用户名以及密码换取access_token的),一般需要携带的参数:
    • client_id:如上
    • client_secret:可以理解为客户端的密码,做过APP的肯定都接触过使用微博登录的话是需要在微博申请app_idapp_secret的,其实对应就是client_idclient_secret
    • code: 第一步请求获取的authorization_code
    • grant_type:认证类型,也可以理解为本次请求需要做什么,这个属性各个服务器定义非常不同,都不是按照OAuth标准协议来的,各有各的任性,不过通常意义上都实现了下面五种类型:
      • authorization_code:即本次讲解的流程,需要先请求code,再用codetoken
      • password:直接使用用户名和密码交换token
      • refresh_token:刷新token
      • implicit:简单模式,一般用在JS等直接在浏览器里获取token的方式,当然密码也是直接放在参数里的,尽管加了密,想想也不太安全(经 @251be0f15727 纠正请看评论),很少用
      • client_credentials:无用户模式,即直接客户端的服务器单凭自己的client_idclient_secret请求资源,一般用于请求一些服务器的非私密信息,使用极少
    • 如果不是浏览器,还需要加上usernamepassword即用户的登录信息

上述1,2,3举得是标准全量流程,使用不同的客户端方式可能会有不同,但是大的流程是不变的。获取到access_token后就可以使用token向微博请求个人信息了,比如昵称和头像;也可以使用token刷新access_token

使用GitHub登录实战演练#####

先演示如何搭建客户端,选择GitHub作为OAuth认证服务器,需要在GitHub Settings注册客户端

3. spring security & oauth2_第2张图片
注册测试客户端

演示所用客户端配置如图
3. spring security & oauth2_第3张图片
localhost配置

需要注意的参数主要是 callback URL,需要和传递的参数完全一致,对应上面的 redirect_uri解释。

修改WebSecurityConfig

    @Autowired
    OAuth2ClientContext oauth2ClientContext;
...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .anyRequest().permitAll()
                .and().exceptionHandling()
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
//                .and()
//                .formLogin().loginPage("/login").loginProcessingUrl("/login.do").defaultSuccessUrl("/user/info")
//                .failureUrl("/login?err=1")
//                .permitAll()
                .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .permitAll()

                .and().addFilterBefore(sso(), BasicAuthenticationFilter.class)
        ;

    }

    private Filter sso() {
        OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/github");
        OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext);
        githubFilter.setRestTemplate(githubTemplate);
        githubFilter.setTokenServices(new UserInfoTokenServices(githubResource().getUserInfoUri(), github().getClientId()));
        return githubFilter;
    }

    @Bean
    @ConfigurationProperties("github.resource")
    public ResourceServerProperties githubResource() {
        return new ResourceServerProperties();
    }

    @Bean
    @ConfigurationProperties("github.client")
    public AuthorizationCodeResourceDetails github() {
        return new AuthorizationCodeResourceDetails();
    }

    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(
            OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }

这就是主要代码了,标准流程spring-security-oauth2已经都帮我们写好了,代码解释:

  1. addFilterBefore(sso(), BasicAuthenticationFilter.class)在网站基本认证之前添加OAuth2ClientAuthenticationProcessingFilter,上面介绍的OAuth流程基本都在这个Filter里,建议翻看源码有助于理解
  2. @ConfigurationProperties注解是方便通过配置文件生成所需类,这里因为OAuth配置比较复杂,故将properties文件改用YAML模式,具体配置下面讲解。
  3. 注册一个额外的FilterOAuth2ClientContextFilter,主要作用是重定向,当遇到需要权限的页面或URL,代码抛出异常,这时这个Filter将重定向到OAuth鉴权的地址,本文即/login/github
  4. 之后向GitHub获取code包括token的功能spring都帮我们封装好了,非常方便,如果要自定义,只要重写对应的接口即可,就像上篇文章里重写UserDetailsService是一样的

配置文件:

github:
  client:
    clientId: fd57ea20f71057e0f396
    clientSecret: 30b73085b6c726b5fb2e0fa8402846b72d86451f
    accessTokenUri: https://github.com/login/oauth/access_token
    userAuthorizationUri: https://github.com/login/oauth/authorize
    authenticationScheme: query
    clientAuthenticationScheme: form
#    pre-established-redirect-uri: http://localhost:8090/login/github
#    registered-redirect-uri: http://localhost:8090/login/github
#    use-current-uri: false
  resource:
    userInfoUri: https://api.github.com/user

clientIdclientSecret在上文注册客户端的Github页面上有,accessTokenUri是获取token的地址,userAuthorizationUri是登陆之后询问的地址,只是第一次会有,张这样:

3. spring security & oauth2_第4张图片
询问

使用过微博登录的同学肯定也见过微博的这个页面,现在大家应该知道这个页面是怎么来了的吧,后面2个 Schema是指请求参数以什么样的方式跟随,有 query(跟在url参数后面), form(以form的body形式提交), header(放到Http header里), none(没有,我也不知道这个是什么意思),注释的3个是测试回调地址的,默认是将当前url地址作为 redirect_uri,因为有些服务器是允许多个回调地址的,这个具体看服务器自己怎么设定。这些参数都可以在对应 ResourceServerPropertiesAuthorizationCodeResourceDetails的源码找到。


最后,页面代码就不贴了,就是一个a标签,链接到/login/github,运行成功的话,在user页面应该可以看到你的github的用户名

3. spring security & oauth2_第5张图片
kaenry使用github登录后的页面

本文到此结束,再写实在太长了,下文将介绍如何使用 spring-security-oauth2OAuth Server,我想这才是重点。另外提一点,上面的代码不完整,只支持github登录(有兴趣的同学可以添加诸如FaceBook登录啊什么的,改改配置文件和地址就行了),本来的 admin/admin将失去效果,可以使用ajax提交form的形式代替,就几行代码:

@RequestMapping(value = "/ajaxLogin", method = RequestMethod.POST)
    @ResponseBody
    public String ajaxLogin(@RequestParam String username, @RequestParam String password) {
        try {
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
            token.setDetails(loginService.loadUserByUsername(username));
            Authentication auth = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
            return JsonResponse.returnResult(true);
        } catch (UsernameNotFoundException e1) {
            LOGGER.error("ajax login error, username not found", e1);
            return JsonResponse.returnMsg(false,"login.passwordError");
        } catch (BadCredentialsException e2) {
            LOGGER.error("ajax login error, password not right", e2);
            return JsonResponse.returnMsg(false,"login.passwordError");
        } catch (Exception e){
            LOGGER.error("ajax login error. forbidden login status", e);
            return JsonResponse.returnMsg(false,"login.forbidden");
        }
    }

这里后面会搭建OAuth server就不赘述了,代码打了tag在github v1.2。

PS:上面的代码其实为了方遍理解OAuth流程,spring有个注解叫@EnableOAuth2Sso(这里用的是它的子集EnableOAuth2Client)可以一键搞定,一行代码都不用写,有兴趣的可以看一看。.

你可能感兴趣的:(3. spring security & oauth2)