【Kotlin Spring Boot 服務(wù)端開發(fā): 問題集錦】 Spring Security : 自定義AccessDeniedHandler 處理 Ajax 請求

【Kotlin Spring Boot 服務(wù)端開發(fā): 問題集錦】 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 的默認(rèn)實(shí)現(xiàn)是 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;
    }
}

自定義實(shí)現(xiàn) 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": "沒有權(quán)限"
                    }
                """.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 方法級(jí)安全
@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/**" // 不攔截靜態(tài)資源
            ).permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/login")// url 請求路徑,對(duì)應(yīng) 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();
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市虹曙,隨后出現(xiàn)的幾起案子规婆,更是在濱河造成了極大的恐慌挠唆,老刑警劉巖糙申,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐佳,死亡現(xiàn)場離奇詭異涨冀,居然都是意外死亡填硕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門鹿鳖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扁眯,“玉大人,你說我怎么就攤上這事翅帜∫鎏矗” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵涝滴,是天一觀的道長绣版。 經(jīng)常有香客問我歼疮,道長僵娃,這世上最難降的妖魔是什么腋妙? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮骤素,結(jié)果婚禮上匙睹,老公的妹妹穿的比我還像新娘。我一直安慰自己济竹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布送浊。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唁桩。 梳的紋絲不亂的頭發(fā)上闭树,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音荒澡,去河邊找鬼。 笑死单山,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的米奸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悴晰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了膨疏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤佃却,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后饲帅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡育八,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年赦邻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了髓棋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惶洲。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恬吕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铐料,我是刑警寧澤豺旬,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布柒凉,位于F島的核電站,受9級(jí)特大地震影響扛拨,放射性物質(zhì)發(fā)生泄漏举塔。R本人自食惡果不足惜绑警,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一央渣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芽丹,春花似錦、人聲如沸拔第。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懈涛。三九已至泳猬,卻和暖如春批钠,著一層夾襖步出監(jiān)牢的瞬間得封,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工忙上, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疫粥。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像手形,于是被迫代替她去往敵國和親啥供。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容