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表单
<form action="/login.do" method="post">
            <input type="hidden" th:value="${_csrf.token}" name="_csrf"/>
            <div class="form-group">
                <label for="usernameInput">Username</label>
                <input type="text" id="usernameInput" class="form-control" name="username" placeholder="enter your username"/>
            </div>
            <div class="form-group">
                <label for="passwordInput">Password</label>
                <input type="password" id="passwordInput" class="form-control" name="password" placeholder="enter your password"/>
            </div>
            <div class="form-group" th:if="${err}">
                <div class="text-danger ">
                    用户名或密码错误
                </div>
            </div>
            <div class="form-group text-center">
                <button type="submit" class="btn btn-primary">Login</button>
                <button type="reset" class="btn btn-warning">Reset</button>
            </div>
        </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协议流程######

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

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注册客户端

注册测试客户端

演示所用客户端配置如图

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是登陆之后询问的地址,只是第一次会有,张这样:

询问

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


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

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就不赘述了,代码打了taggithub v1.2

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博文

http状态码一览表

http状态码一览表 1**:请求收到,继续处理 2**:操作成功收到,分析、接受 3**:完成此请求必须进一步处理 4**:请求包含一个错误语法或不能完成 5...

38770
来自专栏V站

Servlet HTTP 状态码大全列表

状态行包括 HTTP 版本(在本例中为 HTTP/1.1)、一个状态码(在本例中为 200)和一个对应于状态码的短消息(在本例中为 OK)。

17020
来自专栏草根专栏

使用Identity Server 4建立Authorization Server (3)

上一部分简单的弄了个web api 并通过Client_Credentials和ResourceOwnerPassword两种方式获取token然后进行api请...

34160
来自专栏程序猿DD

从零开始的Spring Security Oauth2(一)

前言 今天来聊聊一个接口对接的场景,A厂家有一套HTTP接口需要提供给B厂家使用,由于是外网环境,所以需要有一套安全机制保障,这个时候oauth2就可以作为一个...

46260
来自专栏菜鸟程序员

Burpsuite插件的使用

21150
来自专栏python学习指南

python爬虫(七)_urllib2:urlerror和httperror

urllib2的异常错误处理 在我们用urlopen或opener.open方法发出一个请求时,如果urlopen或opener.open不能处理这个respo...

24880
来自专栏battcn

一起来学SpringBoot | 第十五篇:actuator与spring-boot-admin 可以说的秘密

一起来学SpringBoot | 第十四篇:强大的 actuator 服务监控与管理 中介绍了 actuator 的作用,细心的朋友可能会发现通过 http r...

19920
来自专栏Kirito的技术分享

Re:从零开始的Spring Session(三)

上一篇文章中,我们使用Redis集成了Spring Session。大多数的配置都是Spring Boot帮我们自动配置的,这一节我们介绍一点Spring Se...

372110
来自专栏ImportSource

一分钟了解 Keep-Alive

默认的HTTP连接(connection)通常都会在每次request完成后就关闭掉,这也就意味着server端会在发送了一个response之后就把TCP 连...

42770
来自专栏一场梦

Servlet HTTP 状态码大全列表

13530

扫码关注云+社区

领取腾讯云代金券