使用Maven(IDEA)来初步整合使用 SpringBoot 2 与Spring Security 5

一、Spring Security简介

  • Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
  • Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
  • Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。
  • 百度百科——Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
    在这里插入图片描述

二、Spring Security的新特性

Spring Security 官方文档

Spring Security 5.1 中文文档下载;提取码:801i

  • 1、OAuth2

    OAuth2 Resource Server
    添加了对 OAuth2 资源服务器的基本支持。见 oauth2resourceserver

  • 2、Authorization Code Flow

    用户现在可以使用OAuth 2.0 Authorization Code grant 获取访问令牌(access token)。请查看 authcodegrant 示例。

  • 3、支持 WebClient 和 OAuth2

    现在内置了对 OAuth2 和 WebClient 的支持,该支持将允许:

    • 将访问令牌添加到请求中

    • 访问令牌过期时自动刷新

    • 解析要使用的访问令牌

  • 4、WebFlux OAuth2 Log In Supports OIDC

    WebFlux 应用程序现在可以使用 OAuth2 和 OIDC 进行身份验证。有关详细示例,请查看 oauth2login-webflux。

  • 5、配置改进

    改进默认登录页面,更现代化
    默认登录页面实用了 HTML5,变得更现代化,看起来更具视觉吸引力。

  • 6、默认注销页面

    由于添加了 CSRF 注销保护,因此默认应用程序无法注销。现在,如果正在使用默认登录页面(例如没有配置登录页面),那么还会有一个默认的注销页面,它显示了一个注销表单。

  • 7、简化 RequestCache 配置

    用户现在可以配置默认的 RequestCache,用于将其暴露给@Bean。

更多请参考官方文档

三、SpringBoot 2.X和 Spring Security5.X的整合(hello world版本)

1、使用idea的Spring Initializr来初始化项目

在这里插入图片描述
导入Web模块的starter和Security
在这里插入图片描述

2、查看SpringBoot 与Spring Security 的版本

可以打开pom.xml。右键查看依赖的版本控制在这里插入图片描述在这里插入图片描述 在这里插入图片描述

3、直接运行主程序类。

直接访问 http://localhost:8080,默认会追加上/login
在这里插入图片描述

4、分析

目录结构只是Maven生成的。我们什么都没做。为什么?
在这里插入图片描述
有兴趣的可以去官方文档查找答案。

在这里我就直接说了:
1、我们打开主配置类;

2、摁两下shift弹出搜索框,然后搜索WebSecurityConfigurerAdapter,我们来打开这个类;(也可以直接在左边的项目工程中,找到External Libraries我们来找到这个org.springframework.security.config.annotation.web.configuration)也是找到这个类。


protected void configure(HttpSecurity http) throws Exception {
     this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
     ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests()
     .anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
 }

3、在WebSecurityConfigurerAdapter这个抽象类中,我们搜索formlogin来查看这方法。

public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
	return (FormLoginConfigurer)this.getOrApply(new FormLoginConfigurer());
}
//可以看到它返回的是FormLoginConfigurer<HttpSecurity>这个类型的数据,我们继续点进去(ctrl+鼠标左键),查看FormLoginConfigurer这个类

4、查看FormLoginConfigurer类翻到最下面。

private void initDefaultLoginFilter(H http) {
    DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = (DefaultLoginPageGeneratingFilter)http.getSharedObject(DefaultLoginPageGeneratingFilter.class);
    if (loginPageGeneratingFilter != null && !this.isCustomLoginPage()) {
        loginPageGeneratingFilter.setFormLoginEnabled(true);
        loginPageGeneratingFilter.setUsernameParameter(this.getUsernameParameter());
        loginPageGeneratingFilter.setPasswordParameter(this.getPasswordParameter());
        loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());
        loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());
        loginPageGeneratingFilter.setAuthenticationUrl(this.getLoginProcessingUrl());
    }
}
//可以看到这个方法做了我们默认的登录页是否存在的判断,大家可以自己查看isCustomLoginPage()
//这个方法返回的是布尔值,方法的含义的我个人理解是:是默认登录页吗?
//如果不是的话,loginPageGeneratingFilter不是空的话,就给loginPageGeneratingFilter做一些设置。

5、这个过滤器做了什么事情?
我们点DefaultLoginPageGeneratingFilter查看这个类。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)req;
    HttpServletResponse response = (HttpServletResponse)res;
    boolean loginError = this.isErrorPage(request);
    boolean logoutSuccess = this.isLogoutSuccess(request);
    if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
        chain.doFilter(request, response);
    } else {
        String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
        response.setContentType("text/html;charset=UTF-8");
        response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
        response.getWriter().write(loginPageHtml);
    }
}
//可以看到,登录页面是由generateLoginPageHtml()方法构造的

6、generateLoginPageHtml()方法就在dofilte方法下面,也可以直接点过去

private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess) {
    String errorMsg = "Invalid credentials";
    if (loginError) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            AuthenticationException ex = (AuthenticationException)session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
            errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
        }
    }

    StringBuilder sb = new StringBuilder();
    sb.append("<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"\">\n    <title>Please sign in</title>\n    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n  </head>\n  <body>\n     <div class=\"container\">\n");
    String contextPath = request.getContextPath();
    if (this.formLoginEnabled) {
        sb.append("      <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n        <h2 class=\"form-signin-heading\">Please sign in</h2>\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "        <p>\n          <label for=\"username\" class=\"sr-only\">Username</label>\n          <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n        </p>\n        <p>\n          <label for=\"password\" class=\"sr-only\">Password</label>\n          <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n        </p>\n" + this.createRememberMe(this.rememberMeParameter) + this.renderHiddenInputs(request) + "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n      </form>\n");
    }

    if (this.openIdEnabled) {
        sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "        <p>\n          <label for=\"username\" class=\"sr-only\">Identity</label>\n          <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n        </p>\n" + this.createRememberMe(this.openIDrememberMeParameter) + this.renderHiddenInputs(request) + "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n      </form>\n");
    }

    if (this.oauth2LoginEnabled) {
        sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");
        sb.append(createError(loginError, errorMsg));
        sb.append(createLogoutSuccess(logoutSuccess));
        sb.append("<table class=\"table table-striped\">\n");
        Iterator var7 = this.oauth2AuthenticationUrlToClientName.entrySet().iterator();

        while(var7.hasNext()) {
            Entry<String, String> clientAuthenticationUrlToClientName = (Entry)var7.next();
            sb.append(" <tr><td>");
            String url = (String)clientAuthenticationUrlToClientName.getKey();
            sb.append("<a href=\"").append(contextPath).append(url).append("\">");
            String clientName = HtmlUtils.htmlEscape((String)clientAuthenticationUrlToClientName.getValue());
            sb.append(clientName);
            sb.append("</a>");
            sb.append("</td></tr>\n");
        }

        sb.append("</table>\n");
    }

    sb.append("</div>\n");
    sb.append("</body></html>");
    return sb.toString();
}

我们的页面呢就是这样一步步构造出来的。

5、debug=true

接下来我们在资源文件夹下面的application.properties文件中加一行代码debug=true

然后我们需要编写一个简单的controler

package com.sjt.springbootsecurity.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author easychill
 * @version V1.0
 * @Package com.sjt.springbootsecurity.controller
 * @date 2019/7/31 13:13
 */
@RestController
public class TestController {
    @GetMapping("/")
    public String hello() {
        return "hello security!";
    }
}

然后重新把项目跑一遍,不要着急,我们可以看到控制台打印了一些自动匹配的配置类,和没有匹配的。

翻到很上面的一个位置应该可以看到如下内容:在这里插入图片描述
然后复制密码,刷新我们的页面 http://localhost:8080,还是来到了登录页面。账号是user,密码是你复制的那个。登录…然后就可以看到这个东西
在这里插入图片描述
奇怪吗?账号密码是哪里来的?

6、账号密码从哪来?

按照之前的套路,我们直接,shift+shift,搜索UserDetailsServiceAutoConfiguration,在这个类中,可以看到getOrDeducePassword()这个方法,这样密码为什么会出现在打印台就一目了然了。在这个方法的形参中我们看到了User类!我们根本没有定义过,说明是封装好的。点进去!
在这里插入图片描述
这样的话,就很清楚了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值