四Spring Security使用自定义认证页面

Posted 上善若水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了四Spring Security使用自定义认证页面相关的知识,希望对你有一定的参考价值。

一、 SpringSecurity使用自定义认证页面

1.1、在Spring Security主配置文件中指定认证页面配置信息

spring-security.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/security
            http://www.springframework.org/schema/security/spring-security.xsd">
    <!--直接释放无需经过SpringSecurity过滤器的静态资源-->
    <security:http pattern="/css/**" security="none"/>
    <security:http pattern="/img/**" security="none"/>
    <security:http pattern="/plugins/**" security="none"/>
    <security:http pattern="/failer.jsp" security="none"/>
    <security:http pattern="/favicon.ico" security="none"/>

    <!--
        设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)
        auto-config="true"  表示自动加载Spring Security的配置文件
        use-expressions="true"  表示使用Spring的el表达式来配置Spring Security
    -->
    <security:http auto-config="true" use-expressions="true">
        <!-- 指定login.jsp页面可以被匿名访问 -->
        <security:intercept-url pattern="/login.jsp" access="permitAll()"/>
        <!--
            使用spring的el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色
            pattern="/**" 表示拦截所有资源
            access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER角色才能访问资源
         -->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>
        <!--指定自定义的认证页面-->
        <security:form-login login-page="/login.jsp" login-processing-url="/login"
                             default-target-url="/index.jsp" authentication-failure-url="/failer.jsp"/>
        <!--指定退出登录后跳转的页面-->
        <security:logout logout-url="/logout" logout-success-url="/login.jsp"/>
    </security:http>

    <!--设置Spring Security认证用户信息的来源-->
    <!--Spring Security默认的认证必须是加密的,加上{noop}表示不加密认证-->
    <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <security:user name="user" password="{noop}user"
                               authorities="ROLE_USER" />
                <security:user name="admin" password="{noop}admin"
                               authorities="ROLE_ADMIN" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

修改认证页面的请求地址

再次启动项目后就可以看到自定义的酷炫认证页面了!

然后你开开心心的输入了用户名 user,密码user,就出现了如下的界面:

403 什么异常?这是SpringSecurity中的权限不足!这个异常怎么来的?还记得上面SpringSecurity内置认证页面源码中的那个_csrf隐藏input吗?问题就在这了!

1.2、SpringSecurity 的csrf防护机制

CSRF(Cross-site request forgery)跨站请求伪造,是一种难以防范的网络攻击方式。

1.2.1、SpringSecurity中CsrfFilter过滤器说明

package org.springframework.security.web.csrf;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

public final class CsrfFilter extends OncePerRequestFilter {
    public static final RequestMatcher DEFAULT_CSRF_MATCHER = new CsrfFilter.DefaultRequiresCsrfMatcher();
    private final Log logger = LogFactory.getLog(this.getClass());
    private final CsrfTokenRepository tokenRepository;
    private RequestMatcher requireCsrfProtectionMatcher;
    private AccessDeniedHandler accessDeniedHandler;

    public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
        this.requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
        this.accessDeniedHandler = new AccessDeniedHandlerImpl();
        Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
        this.tokenRepository = csrfTokenRepository;
    }
	//通过这里可以看出SpringSecurity的csrf机制把请求方式分成两类来处理
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        request.setAttribute(HttpServletResponse.class.getName(), response);
        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        boolean missingToken = csrfToken == null;
        if (missingToken) {
            csrfToken = this.tokenRepository.generateToken(request);
            this.tokenRepository.saveToken(csrfToken, request, response);
        }

        request.setAttribute(CsrfToken.class.getName(), csrfToken);
        request.setAttribute(csrfToken.getParameterName(), csrfToken);
		//第一类:"GET", "HEAD", "TRACE", "OPTIONS"四类请求可以直接通过
        if (!this.requireCsrfProtectionMatcher.matches(request)) {
            filterChain.doFilter(request, response);
        } else {
			//第二类:除去上面四类,包括POST都要被验证携带token才能通过
            String actualToken = request.getHeader(csrfToken.getHeaderName());
            if (actualToken == null) {
                actualToken = request.getParameter(csrfToken.getParameterName());
            }

            if (!csrfToken.getToken().equals(actualToken)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request));
                }

                if (missingToken) {
                    this.accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken));
                } else {
                    this.accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));
                }

            } else {
                filterChain.doFilter(request, response);
            }
        }
    }

    public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
        Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
        this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
    }

    public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
        Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
        this.accessDeniedHandler = accessDeniedHandler;
    }

    private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
        private final HashSet<String> allowedMethods;

        private DefaultRequiresCsrfMatcher() {
            this.allowedMethods = new HashSet(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
        }

        public boolean matches(HttpServletRequest request) {
            return !this.allowedMethods.contains(request.getMethod());
        }
    }
}

通过源码分析,我们明白了,自己的认证页面,请求方式为POST,但却没有携带token,所以才出现了403权限不
足的异常。那么如何处理这个问题呢?
方式一:直接禁用csrf,不推荐。
方式二:在认证页面携带token请求。

1.2.2、禁用csrf防护机制(不推荐)

在SpringSecurity主配置文件中添加禁用crsf防护的配置。

	<!--
        设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)
        auto-config="true"  表示自动加载Spring Security的配置文件
        use-expressions="true"  表示使用Spring的el表达式来配置Spring Security
    -->
    <security:http auto-config="true" use-expressions="true">
        <!-- 指定login.jsp页面可以被匿名访问 -->
        <security:intercept-url pattern="/login.jsp" access="permitAll()"/>
        <!--
            使用spring的el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色
            pattern="/**" 表示拦截所有资源
            access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER角色才能访问资源
         -->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>
        <!--指定自定义的认证页面-->
        <security:form-login login-page="/login.jsp" login-processing-url="/login"
                             default-target-url="/index.jsp" authentication-failure-url="/failer.jsp"/>
        <!--指定退出登录后跳转的页面-->
        <security:logout logout-url="/logout" logout-success-url="/login.jsp"/>
        <!--禁用csrf防护机制-->
        <security:csrf disabled="true"/>
    </security:http>

1.2.3、 在认证页面携带token请求


注: HttpSessionCsrfTokenRepository对象负责生成token并放入session域中。

1.2.4、运行


以上是关于四Spring Security使用自定义认证页面的主要内容,如果未能解决你的问题,请参考以下文章

认证与授权Spring Security自定义页面

认证与授权Spring Security自定义页面

认证与授权Spring Security自定义页面

认证与授权Spring Security自定义页面

田帅spring security教程第二章: 自定义登录认证流程

spring boot整合 spring security之自定义认证