Java经典框架之SpringSecurity

SpringSecurity

Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。
   

课程内容的介绍

1. SpringSecurity基本应用
2. SpringSecurity高级应用
  

一、SpringSecurity基本应用

1.初识Spring Security
1.1 Spring Security概念
Spring Security是Spring采用 AOP 思想,基于 servlet过滤器 实现的安全框架。它提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的事实上的标准。
Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足定制需求的能力。
   
特征
对身份验证和授权的全面且可扩展的支持。
保护免受会话固定,点击劫持,跨站点请求伪造等攻击。
Servlet API集成。
与Spring Web MVC的可选集成。
    
1.2 快速入门案例
1.2.1 环境准备
我们准备一个SpringMVC+Spring+jsp的Web环境,然后在这个基础上整合SpringSecurity。
  
首先创建Web项目

Java经典框架之SpringSecurity_第1张图片

    
添加相关的依赖
  
    
      junit
      junit
      4.11
      test
    
    
      org.springframework
      spring-webmvc
      5.2.1.RELEASE
    
    
      javax.servlet
      servlet-api
      2.5
      provided
    
    
      org.slf4j
      slf4j-log4j12
      1.7.25
    
  
   
添加相关的配置文件
Spring配置文件



    


    
SpringMVC配置文件



    


    
  
log4j.properties文件
log4j.rootCategory=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n
  
web.xml



  Archetype Created Web Application

  
  
    contextConfigLocation
    classpath:applicationContext.xml
  
  
    org.springframework.web.context.ContextLoaderListener
  

  
  
    CharacterEncodingFilter
    org.springframework.web.filter.CharacterEncodingFilter
    
      encoding
      utf-8
    
  
  
    CharacterEncodingFilter
    /*
  
  
  
    dispatcherServletb
    org.springframework.web.servlet.DispatcherServlet
    
    
      contextConfigLocation
      classpath:spring-mvc.xml
    
    1
  
  
    dispatcherServletb
    
    /
  


   
添加Tomcat的插件 启动测试

    
        org.apache.tomcat.maven
        tomcat7-maven-plugin
        2.2
        
            8082
            /
        
    
   

Java经典框架之SpringSecurity_第2张图片

    
1.2.2 整合SpringSecurity
添加相关的依赖
spring-security-core.jar 核心包,任何SpringSecurity的功能都需要此包。
spring-security-web.jar:web工程必备,包含过滤器和相关的web安全的基础结构代码。
spring-security-config.jar:用于xml文件解析处理。
spring-security-tablibs.jar:动态标签库。
    
    
      org.springframework.security
      spring-security-config
      5.1.5.RELEASE
    
    
      org.springframework.security
      spring-security-taglibs
      5.1.5.RELEASE
    
    
web.xml文件中配置SpringSecurity。
  
  
    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy
  
  
    springSecurityFilterChain
    /*
  
   
添加SpringSecurity的配置文件。


    
    
    
        
        

    
    
    
        
            
                
                
                
            
        
    
   
将SpringSecurity的配置文件引入到Spring中。

Java经典框架之SpringSecurity_第3张图片

  
启动测试访问

Java经典框架之SpringSecurity_第4张图片

  
2. 认证操作
2.1 自定义登录页面
如何使用我们自己写的登录页面呢?
<%--
  Created by IntelliJ IDEA.
  User: dpb
  Date: 2021/3/16
  Time: 16:57
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>


    Title


    

登录页面

账号:
密码:
  
修改相关的配置文件。


    
    
    
        
        
        
        

        
        
        
        
    
    
    
        
            
                
                
                
            
        
    
   

Java经典框架之SpringSecurity_第5张图片

  
访问home.jsp页面后会自动跳转到自定义的登录页面,说明这个需求是实现了。

Java经典框架之SpringSecurity_第6张图片

  
但是当我们提交了请求后页面出现了如下的错误。
  

Java经典框架之SpringSecurity_第7张图片

    
2.2 关闭CSRF拦截
为什么系统默认的登录页面提交没有CRSF拦截的问题呢?

Java经典框架之SpringSecurity_第8张图片

 
我自定义的认证页面没有这个信息怎么办呢?两种方式:
关闭CSRF拦截

Java经典框架之SpringSecurity_第9张图片

  
登录成功~
   
使用CSRF防护
在页面中添加对应taglib

Java经典框架之SpringSecurity_第10张图片

  
我们访问登录页面

Java经典框架之SpringSecurity_第11张图片

  
登录成功

Java经典框架之SpringSecurity_第12张图片

  
2.3 数据库认证
前面的案例我们的账号信息是直接写在配置文件中的,这显然是不太好的,我们来介绍小如何实现和数据库中的信息进行认证。
  
添加相关的依赖
    
      org.mybatis
      mybatis
      3.5.4
    
    
      org.mybatis
      mybatis-spring
      2.0.4
    
    
      mysql
      mysql-connector-java
      8.0.11
    
    
      com.alibaba
      druid
      1.1.8
    
     
添加配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/logistics?characterEncoding=utf-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456



    

    
    

    
    
        
        
        
        
     
    
        
        
        
    

    
        
    
  
需要完成认证的service中继承 UserDetailsService父接口。

Java经典框架之SpringSecurity_第13张图片

  
实现类中实现验证方法。
package com.bobo.service.impl;

import com.bobo.mapper.UserMapper;
import com.bobo.pojo.User;
import com.bobo.pojo.UserExample;
import com.bobo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper mapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 根据账号查询用户信息
        UserExample example = new UserExample();
        example.createCriteria().andUserNameEqualTo(s);
        List users = mapper.selectByExample(example);
        if(users != null && users.size() > 0){
            User user = users.get(0);
            if(user != null){
                List authorities = new ArrayList<>();
                // 设置登录账号的角色
                authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
                UserDetails userDetails = new org.springframework.security.core.userdetails.User(
                        user.getUserName(),"{noop}"+user.getPassword(),authorities
                );
                return userDetails;
            }
        }
        return null;
    }

}
  
最后修改配置文件关联我们自定义的service即可。

Java经典框架之SpringSecurity_第14张图片

  
2.4 加密
在SpringSecurity中推荐我们是使用的加密算法是 BCryptPasswordEncoder。
  
首先生成秘闻

Java经典框架之SpringSecurity_第15张图片

    
修改配置文件

Java经典框架之SpringSecurity_第16张图片

   

Java经典框架之SpringSecurity_第17张图片

   
去掉 {noop}

Java经典框架之SpringSecurity_第18张图片

  
2.5 认证状态
用户的状态包括 是否可用,账号过期,凭证过期,账号锁定等等。

Java经典框架之SpringSecurity_第19张图片

  
我们可以在用户的表结构中添加相关的字段来维护这种关系。
   
2.6 记住我
在表单页面添加一个记住我的按钮。

Java经典框架之SpringSecurity_第20张图片

  
在SpringSecurity中默认是关闭 RememberMe功能的,我们需要放开。

Java经典框架之SpringSecurity_第21张图片

  
这样就配置好了。
  
记住我的功能会方便大家的使用,但是安全性却是令人担忧的,因为Cookie信息存储在客户端很容易被盗取,这时我们可以将这些数据持久化到数据库中。
CREATE TABLE `persistent_logins` (
    `username` VARCHAR (64) NOT NULL,
    `series` VARCHAR (64) NOT NULL,
    `token` VARCHAR (64) NOT NULL,
    `last_used` TIMESTAMP NOT NULL,
    PRIMARY KEY (`series`)
) ENGINE = INNODB DEFAULT CHARSET = utf8
  

Java经典框架之SpringSecurity_第22张图片

   

Java经典框架之SpringSecurity_第23张图片

   
3. 授权
3.1 注解使用
开启注解的支持



        
        

    

    

    
  
jsr250的使用
添加依赖

    javax.annotation
    jsr250-api
    1.0
     
控制器中通过注解设置
package com.bobo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.security.RolesAllowed;

@Controller
@RequestMapping("/user")
public class UserController {

    @RolesAllowed(value = {"ROLE_ADMIN"})
    @RequestMapping("/query")
    public String query(){
        System.out.println("用户查询....");
        return "/home.jsp";
    }
    @RolesAllowed(value = {"ROLE_USER"})
    @RequestMapping("/save")
    public String save(){
        System.out.println("用户添加....");
        return "/home.jsp";
    }

    @RequestMapping("/update")
    public String update(){
        System.out.println("用户更新....");
        return "/home.jsp";
    }
}
  

Java经典框架之SpringSecurity_第24张图片

  

Java经典框架之SpringSecurity_第25张图片

  
Spring表达式的使用
package com.bobo.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.security.RolesAllowed;

@Controller
@RequestMapping("/order")
public class OrderController {

    @PreAuthorize(value = "hasAnyRole('ROLE_USER')")
    @RequestMapping("/query")
    public String query(){
        System.out.println("用户查询....");
        return "/home.jsp";
    }
    @PreAuthorize(value = "hasAnyRole('ROLE_ADMIN')")
    @RequestMapping("/save")
    public String save(){
        System.out.println("用户添加....");
        return "/home.jsp";
    }

    @RequestMapping("/update")
    public String update(){
        System.out.println("用户更新....");
        return "/home.jsp";
    }
}
  
SpringSecurity提供的注解
package com.bobo.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/role")
public class RoleController {

    @Secured("ROLE_USER")
    @RequestMapping("/query")
    public String query(){
        System.out.println("用户查询....");
        return "/home.jsp";
    }

    @Secured("ROLE_ADMIN")
    @RequestMapping("/save")
    public String save(){
        System.out.println("用户添加....");
        return "/home.jsp";
    }

    @RequestMapping("/update")
    public String update(){
        System.out.println("用户更新....");
        return "/home.jsp";
    }
}
  
异常处理
新增一个错误页面,然后在SpringSecurity的配置文件中配置即可。

Java经典框架之SpringSecurity_第26张图片

  

Java经典框架之SpringSecurity_第27张图片

  
当然你也可以使用前面介绍的SpringMVC中的各种异常处理器处理。

Java经典框架之SpringSecurity_第28张图片

  
3.2 标签使用
前面介绍的注解的权限管理可以控制用户是否具有这个操作的权限,但是当用户具有了这个权限后进入到具体的操作页面,这时我们还有进行更细粒度的控制,这时注解的方式就不太适用了,这时我们可以通过标签来处里。
 
添加SpringSecurity的标签库
<%--
  Created by IntelliJ IDEA.
  User: dpb
  Date: 2021/3/16
  Time: 17:02
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>


    Title


    

欢迎光临...

用户查询
用户添加
用户更新
用户删除
  
页面效果

Java经典框架之SpringSecurity_第29张图片

     

二、SpringSecurity高级应用

1. SpringSecurity核心源码分析
分析SpringSecurity的核心原理,那么我们从哪开始分析?以及我们要分析哪些内容?
1. 系统启动的时候SpringSecurity做了哪些事情?
2. 第一次请求执行的流程是什么?
3. SpringSecurity中的认证流程是怎么样的?
  
1.1 系统启动
当我们的Web服务启动的时候,SpringSecurity做了哪些事情?当系统启动的时候,肯定会加载我们配置的web.xml文件。



  Archetype Created Web Application

  
  
    contextConfigLocation
    classpath:applicationContext.xml
  
  
    org.springframework.web.context.ContextLoaderListener
  

  
  
    CharacterEncodingFilter
    org.springframework.web.filter.CharacterEncodingFilter
    
      encoding
      utf-8
    
  
  
    CharacterEncodingFilter
    /*
  
  
  
    dispatcherServletb
    org.springframework.web.servlet.DispatcherServlet
    
    
      contextConfigLocation
      classpath:spring-mvc.xml
    
    1
  
  
    dispatcherServletb
    
    /
  

  
  
    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy
  
  
    springSecurityFilterChain
    /*
  


    
web.xml中配置的信息:
1. Spring的初始化(会加载解析SpringSecurity的配置文件)。
2. SpringMVC的前端控制器初始化。
3. 加载DelegatingFilterProxy过滤器。
  
Spring的初始化操作和SpringSecurity有关系的操作是,会加载介绍SpringSecurity的配置文件,将相关的数据添加到Spring容器中。

Java经典框架之SpringSecurity_第30张图片

  
SpringMVC的初始化和SpringSecurity其实是没有多大关系的。
   
DelegatingFilterProxy过滤器:拦截所有的请求。而且这个过滤器本身是和SpringSecurity没有关系的!!!在之前介绍Shiro的时候,和Spring整合的时候我们也是使用的这个过滤器。 其实就是完成从IoC容器中获取DelegatingFilterProxy这个过滤器配置的 FileterName 的对象。
  
系统启动的时候会执行DelegatingFilterProxy的init方法。
protected void initFilterBean() throws ServletException {
    synchronized(this.delegateMonitor) {
        // 如果委托对象为null 进入
        if (this.delegate == null) {
            // 如果targetBeanName==null
            if (this.targetBeanName == null) {
                // targetBeanName = 'springSecurityFilterChain'
                this.targetBeanName = this.getFilterName();
            }
            // 获取Spring的容器对象
            WebApplicationContext wac = this.findWebApplicationContext();
            if (wac != null) {
                // 初始化代理对象
                this.delegate = this.initDelegate(wac);
            }
        }
    }
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException{
    // springSecurityFilterChain
    String targetBeanName = this.getTargetBeanName();
    Assert.state(targetBeanName != null, "No target bean name set");
    // 从IoC容器中获取 springSecurityFilterChain的类型为Filter的对象
    Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
    if (this.isTargetFilterLifecycle()) {
        delegate.init(this.getFilterConfig());
    }
    return delegate;
}
     

Java经典框架之SpringSecurity_第31张图片

   
init方法的作用是:从IoC容器中获取 FilterChainProxy的实例对象,并赋值给DelegatingFilterProxy的delegate属性。
   
1.2 第一次请求
客户发送请求会经过很多歌Web Filter拦截。

Java经典框架之SpringSecurity_第32张图片

  
然后经过系统启动的分析,我们知道有一个我们定义的过滤器会拦截客户端的所有的请求。
  
DelegatingFilterProxy

Java经典框架之SpringSecurity_第33张图片

  
当用户请求进来的时候会被doFilter方法拦截。
public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain) throws ServletException, IOException {
    Filter delegateToUse = this.delegate;
    if (delegateToUse == null) {
        // 如果 delegateToUse 为空 那么完成init中的初始化操作
        synchronized(this.delegateMonitor) {
            delegateToUse = this.delegate;
            if (delegateToUse == null) {
                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac == null) {
                    throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                }
                delegateToUse = this.initDelegate(wac);
            }
            this.delegate = delegateToUse;
        }
    }
    this.invokeDelegate(delegateToUse, request, response, filterChain);
}
  
invokeDelegate
protected void invokeDelegate(Filter delegate, ServletRequest request,ServletResponse response, FilterChain filterChain) throws ServletException,IOException {
    // delegate.doFilter() FilterChainProxy
    delegate.doFilter(request, response, filterChain);
}
   
所以在此处我们发现DelegatingFilterProxy最终是调用的委托代理对象的doFilter方法。

Java经典框架之SpringSecurity_第34张图片

  
FilterChainProxy
过滤器链的代理对象:增强过滤器链(具体处理请求的过滤器还不是FilterChainProxy ) 根据客户端的请求匹配合适的过滤器链链来处理请求。
public class FilterChainProxy extends GenericFilterBean {
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
    // 过滤器链的集合 保存的有很多个过滤器链 一个过滤器链中包含的有多个过滤器
    private List filterChains;
    private FilterChainProxy.FilterChainValidator filterChainValidator;
    private HttpFirewall firewall;
    // .....
}
  

Java经典框架之SpringSecurity_第35张图片

  
// 处理用户请求
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
    boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    if (clearContext) {
        try {
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
            this.doFilterInternal(request, response, chain);
        } finally {
            SecurityContextHolder.clearContext();
            request.removeAttribute(FILTER_APPLIED);
        }
    } else {
        this.doFilterInternal(request, response, chain);
    }
}
  
doFilterInternal
private void doFilterInternal(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
    FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
    HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
    // 根据当前的请求获取对应的过滤器链
    List filters = this.getFilters((HttpServletRequest)fwRequest);
    if (filters != null && filters.size() != 0) {
        FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
        vfc.doFilter(fwRequest, fwResponse);
    } else {
        if (logger.isDebugEnabled()) {
            logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
        }
        fwRequest.reset();
        chain.doFilter(fwRequest, fwResponse);
    }
}
   
获取到了对应处理请求的过滤器链。

Java经典框架之SpringSecurity_第36张图片

  
SpringSecurity中处理请求的过滤器中具体处理请求的方法。
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    if (this.currentPosition == this.size) {
        if (FilterChainProxy.logger.isDebugEnabled()) {
            FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest)+ " reached end of additional filter chain; proceeding with original chain");
        }
        this.firewalledRequest.reset();
        this.originalChain.doFilter(request, response);
    } else {
        ++this.currentPosition;
        Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
        if (FilterChainProxy.logger.isDebugEnabled()) {
            FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest)+ " at position " + this.currentPosition + " of " + this.size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'");
        }
        nextFilter.doFilter(request, response, this);
    }
}
  

Java经典框架之SpringSecurity_第37张图片

  
主要过滤器的介绍
https://www.processon.com/view/link/5f7b197ee0b34d0711f3e955
    
ExceptionTranslationFilter

ExceptionTranslationFilter是我们看的过滤器链中的倒数第二个,作用是捕获倒数第一个过滤器抛出来的异常信息。

Java经典框架之SpringSecurity_第38张图片

  
FilterSecurityInterceptor
做权限相关的内容
public void invoke(FilterInvocation fi) throws IOException, ServletException{
    if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filter Applied") != null && this.observeOncePerRequest) {
        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    } else {
        if (fi.getRequest() != null && this.observeOncePerRequest) {
            fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
        }
        // 抛出异常 ExceptionTranslationFilter就会捕获异常
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.finallyInvocation(token);
        }
        super.afterInvocation(token, (Object)null);
    }
}
  
ExceptionTranslationFilter 处理异常的代码。

Java经典框架之SpringSecurity_第39张图片

  

Java经典框架之SpringSecurity_第40张图片

  

Java经典框架之SpringSecurity_第41张图片

    

Java经典框架之SpringSecurity_第42张图片

  

Java经典框架之SpringSecurity_第43张图片

  

Java经典框架之SpringSecurity_第44张图片

  

Java经典框架之SpringSecurity_第45张图片

  
当用第二次提交 http://localhost:8082/login时 我们要关注的是 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
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("\n\n \n \n \n \n \n Please signin\n \n \n \n \n \n");

    String contextPath = request.getContextPath();
    if (this.formLoginEnabled) {
        sb.append(" 
\n

Please sign in

\n" + createError(loginError, errorMsg) +createLogoutSuccess(logoutSuccess) + "

\n \n \n

\n

\n \n \n

\n" + this.createRememberMe(this.rememberMeParameter) + this.renderHiddenInputs(request) + "\n
\n"); } if (this.openIdEnabled) { sb.append("
\n\n" +createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "

\n \n\n

\n" + this.createRememberMe(this.openIDrememberMeParameter) +this.renderHiddenInputs(request) + " \n
\n"); } if (this.oauth2LoginEnabled) { sb.append(""); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("\n"); Iterator var7 = this.oauth2AuthenticationUrlToClientName.entrySet().iterator(); while(var7.hasNext()) { Entry clientAuthenticationUrlToClientName =(Entry)var7.next(); sb.append(" \n"); } sb.append("
"); String url = (String)clientAuthenticationUrlToClientName.getKey(); sb.append(""); String clientName = HtmlUtils.htmlEscape((String)clientAuthenticationUrlToClientName.getValue()); sb.append(clientName); sb.append(""); sb.append("
\n"); } sb.append("
\n"); sb.append(""); return sb.toString(); }
   
第一次请求的完整的流程

Java经典框架之SpringSecurity_第46张图片

  
页面调试也可以验证我的推论。

Java经典框架之SpringSecurity_第47张图片

    
1.3 认证流程
UsernamePasswordAuthenticationFilter:专门处理用户认证请求的。
   
在父类中AbstractAuthenticationProcessingFilter看doFilter的逻辑。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)req;
    HttpServletResponse response = (HttpServletResponse)res;
    if (!this.requiresAuthentication(request, response)) {
        // 如果不是必须要认证的请求就直接放过
        chain.doFilter(request, response);
    } else {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Request is to process authentication");
        }
        Authentication authResult;
        try {
            // 获取认证的信息 客户端提交的表单信息
            authResult = this.attemptAuthentication(request, response);
            if (authResult == null) {
                return;
            }
            this.sessionStrategy.onAuthentication(authResult, request, response);
        } catch (InternalAuthenticationServiceException var8) {
            this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
            this.unsuccessfulAuthentication(request, response, var8);
            return;
        } catch (AuthenticationException var9) {
            this.unsuccessfulAuthentication(request, response, var9);
            return;
        }
        if (this.continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        this.successfulAuthentication(request, response, chain, authResult);
    }
}
      
attemptAuthentication在UsernamePasswordAuthenticationFilter实现。
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    } else {
        // 获取表单提交的账号密码
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        // 空处理
        username = username.trim();
        // 账号密码封装为对应的对象
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        // 认证操作
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
   
authenticate

Java经典框架之SpringSecurity_第48张图片

  
变量获取每个认证提供者,然后处理认证。

Java经典框架之SpringSecurity_第49张图片

   
具体处理认证的实现。

Java经典框架之SpringSecurity_第50张图片

  

Java经典框架之SpringSecurity_第51张图片

  

Java经典框架之SpringSecurity_第52张图片

  
此处就会进入到我们自定义的UserServiceImpl中。

Java经典框架之SpringSecurity_第53张图片

  
到这儿就和我们讲的数据库认证连接起来了。
    
2.SpringBoot整合
2.1 整合实现
我们如何在SpringBoot项目里面使用SpringSecurity呢?
   
添加相关的依赖

    org.springframework.boot
    spring-boot-starter-security
  
启动访问

Java经典框架之SpringSecurity_第54张图片

  
账号是:user 密码:控制台中有帮我们自动生成。

Java经典框架之SpringSecurity_第55张图片

   

2.2 自定义登录页面
我们通过Thymeleaf来实现,先创建一个登录页面。



    
    Title


    

登录管理

账号:
密码:
  
添加对应的控制器
@Controller
public class LoginController {
    @RequestMapping("/login.html")
    public String goLoginPage(){
        return "/login";
    }
}
    
添加SpringSecurity的配置类
package com.bobo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 先通过内存中的账号密码来处理
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("zhang")
                .password("{noop}123")
                .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/login_page.html","/login.html","/css/**","/js/**")
                .permitAll()
                .antMatchers("/**")
                .hasAnyRole("USER")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .successForwardUrl("/home.html")
                .and()
                .csrf()
                .disable();
    }
}
  
效果

Java经典框架之SpringSecurity_第56张图片

  
2.3 数据库认证
关键配置

Java经典框架之SpringSecurity_第57张图片

  
2.4 授权

Java经典框架之SpringSecurity_第58张图片

     
3. SpringBoot中的源码分析
前面我们介绍了Spring中整合SpringSecurity的核心源码流程,那么中SpringBoot应该怎么看呢?

Java经典框架之SpringSecurity_第59张图片

  
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, // 初始化
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,// 认证 创建默认的账号密码
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration // 和过滤器链有关
  
默认账号密码

Java经典框架之SpringSecurity_第60张图片

  
DelegatingFilterProxy
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure.security.servlet;

import java.util.EnumSet;
import java.util.stream.Collectors;
import javax.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import
org.springframework.boot.context.properties.EnableConfigurationProperties;
import
org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.http.SessionCreationPolicy;
import
org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({SecurityProperties.class})
@ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class,SessionCreationPolicy.class})
@AutoConfigureAfter({SecurityAutoConfiguration.class})
public class SecurityFilterAutoConfiguration {
    private static final String DEFAULT_FILTER_NAME ="springSecurityFilterChain";
    public SecurityFilterAutoConfiguration() {
    }
    @Bean
    @ConditionalOnBean(
        name = {"springSecurityFilterChain"}
    )
    public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
        // 获取DelegatingFilterProxy对象,并且设置 url-parttern=/*
        DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("springSecurityFilterChain", new ServletRegistrationBean[0]);
        registration.setOrder(securityProperties.getFilter().getOrder());
        registration.setDispatcherTypes(this.getDispatcherTypes(securityProperties));
        return registration;
    }
 
    private EnumSet getDispatcherTypes(SecurityProperties securityProperties) {
        return securityProperties.getFilter().getDispatcherTypes() == null ? null :(EnumSet)securityProperties.getFilter().getDispatcherTypes().stream().map((type)-> {
            return DispatcherType.valueOf(type.name());
        }).collect(Collectors.toCollection(() -> {
            return EnumSet.noneOf(DispatcherType.class);
        }));
    }
}
   

Java经典框架之SpringSecurity_第61张图片

  

Java经典框架之SpringSecurity_第62张图片

   
那么后面的处理又是一样的了,进入FilterChainProxy中。

你可能感兴趣的:(微服务高并发必备技术栈,java,开发语言,SpringSecurity)