【Kotlin Spring Boot 服务端开发: 问题集锦】 Spring Security : 自定义AccessDeniedHandler 处理 Ajax 请求

简介: 【Kotlin Spring Boot 服务端开发: 问题集锦】 Spring Security : 自定义AccessDeniedHandler 处理 Ajax 请求AccessDeniedHandler 接口定义:package org.

【Kotlin Spring Boot 服务端开发: 问题集锦】 Spring Security : 自定义AccessDeniedHandler 处理 Ajax 请求

AccessDeniedHandler 接口定义:

package org.springframework.security.web.access;

import org.springframework.security.access.AccessDeniedException;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Used by {@link ExceptionTranslationFilter} to handle an
 * <code>AccessDeniedException</code>.
 *
 * @author Ben Alex
 */
public interface AccessDeniedHandler {
    // ~ Methods
    // ========================================================================================================

    /**
     * Handles an access denied failure.
     *
     * @param request that resulted in an <code>AccessDeniedException</code>
     * @param response so that the user agent can be advised of the failure
     * @param accessDeniedException that caused the invocation
     *
     * @throws IOException in the event of an IOException
     * @throws ServletException in the event of a ServletException
     */
    void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException,
            ServletException;
}

Spring 的默认实现是 AccessDeniedHandlerImpl :

package org.springframework.security.web.access;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
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.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.WebAttributes;

/**
 * Base implementation of {@link AccessDeniedHandler}.
 * <p>
 * This implementation sends a 403 (SC_FORBIDDEN) HTTP error code. In addition, if an
 * {@link #errorPage} is defined, the implementation will perform a request dispatcher
 * "forward" to the specified error page view. Being a "forward", the
 * <code>SecurityContextHolder</code> will remain populated. This is of benefit if the
 * view (or a tag library or macro) wishes to access the
 * <code>SecurityContextHolder</code>. The request scope will also be populated with the
 * exception itself, available from the key {@link WebAttributes#ACCESS_DENIED_403}.
 *
 * @author Ben Alex
 */
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    // ~ Static fields/initializers
    // =====================================================================================

    protected static final Log logger = LogFactory.getLog(AccessDeniedHandlerImpl.class);

    // ~ Instance fields
    // ================================================================================================

    private String errorPage;

    // ~ Methods
    // ========================================================================================================

    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException,
            ServletException {
        if (!response.isCommitted()) {
            if (errorPage != null) {
                // Put exception into request scope (perhaps of use to a view)
                request.setAttribute(WebAttributes.ACCESS_DENIED_403,
                        accessDeniedException);

                // Set the 403 status code.
                response.setStatus(HttpStatus.FORBIDDEN.value());

                // forward to error page.
                RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
                dispatcher.forward(request, response);
            }
            else {
                response.sendError(HttpStatus.FORBIDDEN.value(),
                    HttpStatus.FORBIDDEN.getReasonPhrase());
            }
        }
    }

    /**
     * The error page to use. Must begin with a "/" and is interpreted relative to the
     * current context root.
     *
     * @param errorPage the dispatcher path to display
     *
     * @throws IllegalArgumentException if the argument doesn't comply with the above
     * limitations
     */
    public void setErrorPage(String errorPage) {
        if ((errorPage != null) && !errorPage.startsWith("/")) {
            throw new IllegalArgumentException("errorPage must begin with '/'");
        }

        this.errorPage = errorPage;
    }
}

自定义实现 MyAccessDeniedHandler

package com.ksb.ksb_with_security.handler

import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.web.WebAttributes
import org.springframework.security.web.access.AccessDeniedHandler
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse


class MyAccessDeniedHandler : AccessDeniedHandler {
    val logger = LoggerFactory.getLogger(MyAccessDeniedHandler::class.java)

    var errorPage: String? = null

    constructor(errorPage: String?) {
        this.errorPage = errorPage
    }

    override fun handle(request: HttpServletRequest, response: HttpServletResponse, accessDeniedException: AccessDeniedException) {
        val isAjax = ControllerTools.isAjaxRequest(request)

        if (!response.isCommitted) {
            if (isAjax) {
                val msg = accessDeniedException.message
                logger.info("accessDeniedException.message = $msg")
                val accessDenyMsg = """
                    {
                    "code":"403",
                    "msg": "没有权限"
                    }
                """.trimIndent()

                ControllerTools.print(response, accessDenyMsg)
            } else if (errorPage != null) {
                // Put exception into request scope (perhaps of use to a view)
                request.setAttribute(WebAttributes.ACCESS_DENIED_403,
                        accessDeniedException)

                // Set the 403 status code.
                response.status = HttpStatus.FORBIDDEN.value()

                // forward to error page.
                val dispatcher = request.getRequestDispatcher(errorPage)
                dispatcher.forward(request, response)
            }
        }
    }
}


object ControllerTools {
    fun isAjaxRequest(request: HttpServletRequest): Boolean {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"), true)
    }

    fun print(response: HttpServletResponse, msg: String) {
        response.characterEncoding = "UTF-8"
        response.contentType = "application/json; charset=utf-8"
        val out = response.writer
        out.append(msg)
        out.flush()
    }
}

然后,在自定义的继承 WebSecurityConfigurerAdapter的配置类WebSecurityConfig 中这样使用上面自定义的MyAccessDeniedHandler

package com.ksb.ksb_with_security.security

import com.ksb.ksb_with_security.handler.MyAccessDeniedHandler
import com.ksb.ksb_with_security.service.MyUserDetailService
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.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.web.access.AccessDeniedHandler

/**
prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]
secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用
jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
 */

@Configuration
@EnableWebSecurity
// 开启 Spring Security 方法级安全
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
class WebSecurityConfig : WebSecurityConfigurerAdapter() {

    @Bean
    fun myAccessDeniedHandler(): AccessDeniedHandler {
        return MyAccessDeniedHandler("/403")
    }

    @Bean
    override fun userDetailsService(): UserDetailsService {
        return MyUserDetailService()
    }

    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http.csrf().disable()
        http.authorizeRequests()
            .antMatchers("/", // 首页不拦截
                    "/css/**",
                    "/fonts/**",
                    "/js/**",
                    "/images/**" // 不拦截静态资源
            ).permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/login")// url 请求路径,对应 LoginController 里面的 @GetMapping("/login")
            .usernameParameter("username")
            .passwordParameter("password")
            .defaultSuccessUrl("/main").permitAll()
            .and()
            .exceptionHandling().accessDeniedHandler(myAccessDeniedHandler())
//            .exceptionHandling().accessDeniedPage("/403")
            .and()
            .logout().permitAll()

        http.logout().logoutSuccessUrl("/")

    }

    @Throws(Exception::class)
    override fun configure(auth: AuthenticationManagerBuilder) {
        //AuthenticationManager 使用我们的 lightSwordUserDetailService 来获取用户信息
        auth.userDetailsService(userDetailsService())
            .passwordEncoder(passwordEncoder())
    }

    /**
     * 密码加密算法
     *
     * @return
     */
    @Bean
    fun passwordEncoder(): BCryptPasswordEncoder {
        return BCryptPasswordEncoder();
    }
}

相关文章
|
2月前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
514 17
Spring Boot 两种部署到服务器的方式
|
18天前
|
前端开发 Java Nacos
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
本文介绍了如何使用Spring Cloud Alibaba 2023.0.0.0技术栈构建微服务网关,以应对微服务架构中流量治理与安全管控的复杂性。通过一个包含鉴权服务、文件服务和主服务的项目,详细讲解了网关的整合与功能开发。首先,通过统一路由配置,将所有请求集中到网关进行管理;其次,实现了限流防刷功能,防止恶意刷接口;最后,添加了登录鉴权机制,确保用户身份验证。整个过程结合Nacos注册中心,确保服务注册与配置管理的高效性。通过这些实践,帮助开发者更好地理解和应用微服务网关。
70 0
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
|
2月前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
59 6
|
2月前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
695 12
|
2月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
101 8
|
3月前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
228 5
|
4月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
109 2
|
5月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
154 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
5月前
|
NoSQL Java Redis
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
这篇文章介绍了Redis的基本命令,并展示了如何使用Netty框架直接与Redis服务器进行通信,包括设置Netty客户端、编写处理程序以及初始化Channel的完整示例代码。
125 1
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
|
5月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
176 2