SpringBoot 整合 Security(一)實現(xiàn)用戶認證并判斷返回json還是view

看這篇文章的我默認都認為是有security基本基礎的宪拥,因為封裝的緣故,沒有基礎的話很容易被繞暈窃蹋。但是只要認真看卡啰,我相信沒什么大問題,如果僅僅是會用警没,該教程讓你更熟悉底層實現(xiàn)匈辱,更優(yōu)雅的寫代碼。

第一章順風車:SpringBoot 整合 Security(一)實現(xiàn)用戶認證并判斷返回json還是view
第二章順風車:SpringBoot 整合 Security(二)實現(xiàn)驗證碼登錄

本教程大概目錄:

  1. 實現(xiàn)用戶認證
  2. 實現(xiàn)json請求返回json杀迹,網(wǎng)頁請求返回網(wǎng)頁亡脸。

1. 添加依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2. 封裝以及實現(xiàn)

因為封裝的比較多,實現(xiàn)比較復雜,我想先把項目結構貼出來浅碾,然后我說明每個類之間的關系大州,然后把類一個一個再貼出來,這樣大家更容易理解一些(我每個類的注釋很全)垂谢。

2.1 結構

他們都在我的 com.fantJ包下厦画。總共也就這么多類滥朱,但是關系比較復雜根暑,我先把每個類介紹一下,按照從上到下的順序徙邻。

  • MyAuthenticationFailHandler.java 自定義登錄失敗處理器排嫌,如果登錄認證失敗,會跳到這個類上來處理缰犁。
  • MyAuthenticationSuccessHandler 自定義登錄成功處理器躏率,如果登錄認證成功,會運行這個類民鼓。
  • 我們可以看出薇芝,不論登錄成功還是失敗,都會路過我們自定義的處理器丰嘉,所以我們可以在這里重寫原來的方法夯到,實現(xiàn)根據(jù)請求頭類型返回相應的 json/view。
  • SimpleResponse 返回類類型 POJO(可返回任意類型的結果)(封裝字符串饮亏、數(shù)字耍贾、集合等返回類型)
  • BrowerSecurityController 登錄路徑請求類,.loginPage("/authentication/require")路幸,是個controller請求類荐开。判斷json/html 請求 返回不同的登錄認證結果
  • BrowserSecurityConfig Security 配置類,它里面會說明登錄方式简肴、登錄頁面晃听、哪個url需要認證、注入登錄失敗/成功過濾器
  • MyUserDetailsService 加載用戶數(shù)據(jù) , 返回UserDetail 實例 (里面包含用戶信息)砰识。
  • BrowserProperties 讀取配置文件里的: fantJ.security.browser.loginPage等 屬性類
  • LoginType 登錄類型 枚舉類
  • SecurityProperties Security 屬性 類能扒,讀取配置文件里的: fantJ.security等屬性,里面包含了BrowserProperties 對象辫狼。
2.2 代碼

我稍微改變下順序初斑,盡量的有條理性,方便大家理清思路膨处。

  1. 我們先寫核心配置類见秤,BrowserSecurityConfig .
package com.fantJ.browser;
/**
 * Security 配置類
 * Created by Fant.J.
 */
@Configuration
public class BrowserSecurityConfig  extends WebSecurityConfigurerAdapter {

    /**
     * 注入 Security 屬性類配置
     */
    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 重寫PasswordEncoder  接口中的方法砂竖,實例化加密策略
     * @return 返回 BCrypt 加密策略
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入 自定義的  登錄成功處理類
     */
    @Autowired
    private MyAuthenticationSuccessHandler mySuccessHandler;
    @Autowired
    private MyAuthenticationFailHandler myFailHandler;
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        String redirectUrl = securityProperties.getBrowser().getLoginPage();
        //basic 登錄方式
//      http.httpBasic()

        //表單登錄 方式
        http.formLogin()
                .loginPage("/authentication/require")
                //登錄需要經(jīng)過的url請求
                .loginProcessingUrl("/authentication/form")
                .successHandler(mySuccessHandler)
                .failureHandler(myFailHandler)
                .and()
                //請求授權
                .authorizeRequests()
                //不需要權限認證的url
                .antMatchers("/authentication/require",redirectUrl).permitAll()
                //任何請求
                .anyRequest()
                //需要身份認證
                .authenticated()
                .and()
                //關閉跨站請求防護
                .csrf().disable();
    }
}

我們可以看到,它里面聲明了登錄頁面url鹃答、哪個請求不需要認證就能訪問乎澄、調用自定義成功/失敗處理過濾器等,可是說是security的核心配置挣跋。

這里面有非常需要注意的一點三圆,就是必須要給loginPage 設置不需要權限認證,否則項目會陷入死鎖避咆,調用loginPage受到權限限制舟肉,然后返回loginPage,然后又受到限制...循環(huán)下去查库。

  1. 上面代碼首先就 找loginPage路媚,那我先把.loginPage("/authentication/require")相關的 視圖控制器 貼出來
package com.fantJ.browser;

/**
 * 判斷json/html 請求 返回不同的結果
 * @ 注解@ResponseStatus :響應狀態(tài)碼 UNAUTHORIZED(401, "Unauthorized")
 * Created by Fant.J.
 */

/**
 * 響應狀態(tài)碼 UNAUTHORIZED(401, "Unauthorized")
 */
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
@RestController
public class BrowerSecurityController {

    /**
     * 日志
     */
    private Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 重定向 策略
     */
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 把當前的請求緩存到 session 里去
     */
    private RequestCache requestCache = new HttpSessionRequestCache();

    /**
     * 注入 Security 屬性類配置
     */
    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 當需要身份認證時 跳轉到這里
     */
    @RequestMapping("/authentication/require")
    public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //拿到請求對象
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest != null){
            //獲取 跳轉url
            String targetUrl = savedRequest.getRedirectUrl();
            logger.info("引發(fā)跳轉的請求是:"+targetUrl);

            //判斷 targetUrl 是不是 .html 結尾, 如果是:跳轉到登錄頁(返回view)
            if (StringUtils.endsWithIgnoreCase(targetUrl,".html")){
                String redirectUrl = securityProperties.getBrowser().getLoginPage();
                redirectStrategy.sendRedirect(request,response,redirectUrl);
            }

        }
        //如果不是,返回一個json 字符串
        return new SimpleResponse("訪問的服務需要身份認證樊销,請引導用戶到登錄頁");
    }
}

其中最主要的邏輯是判斷request請求對象中的getRedirectUrl() 的結果是不是.html 結尾整慎,如果是,則調用sendRedirect(request,response,redirectUrl)方法重定向到redirectUrl頁面围苫,其中redirectUrl是我們自定義的登錄頁面裤园。

如果不是.html 結尾,那就是json請求剂府,我們返回json 字符串提示信息拧揽。SimpleResponse 其實就是一個Object對象,然后實現(xiàn)了它的getter setter方法腺占,為的是結構化封裝淤袜,返回的是對象,而不僅僅是個字符串(返回對象的話衰伯,響應格式是:content:訪問的服務需要身份認證铡羡,請引導用戶到登錄頁 。返回字符串就只是"訪問的服務需要身份認證意鲸,請引導用戶到登錄頁"烦周,你讓接json數(shù)據(jù)的工作者怎么去接這段字符)。

  1. 我就先貼下SimpleResponse 代碼临扮,很簡單论矾,掃一眼就行
package com.fantJ.browser.support;

/**
 * 返回類 工具 (可返回任意類型的結果)
 * Created by Fant.J.
 */
public class SimpleResponse {

    /**
     * 返回 內容 (json格式)
     */
    private Object content;

    public SimpleResponse(Object content) {
        this.content = content;
    }
  ...getter and  setter...
}

  1. 我們在代碼2中的controller中,也調用了 我們自己寫的一個類 SecurityProperties杆勇,它是用來獲取 application.properties 中的配置屬性的。
package com.fantJ.core.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Security 屬性 類
 * Created by Fant.J.
 */
//獲取配置屬性前綴
@ConfigurationProperties(prefix = "fantJ.security")
public class SecurityProperties {
    /**
     * 瀏覽器 屬性類
     */
    private BrowserProperties browser = new BrowserProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }
}

可以看到饱亿。它里面包含了一個對象BrowserProperties 蚜退,它也是讀取配置屬性的一個類

package com.fantJ.core.properties;

/**
 * browser(瀏覽器)配置文件里的: fantJ.security.browser.loginPage 屬性類
 * Created by Fant.J.
 */
public class BrowserProperties {

    /**
     *  loginPage 默認值  是login.html
     *  如果 application.properties 里有對 fantJ.security.browser.loginPage 的聲明闰靴,則獲取該值
     */
    private String loginPage = "/browser-login.html";

    /**
     * 默認 返回 json 類型
     */
    private LoginType loginType = LoginType.JSON;

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }

    public LoginType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginType loginType) {
        this.loginType = loginType;
    }
}

然后我把配置文件貼出來。

#登錄頁 配置
fantJ.security.browser.loginPage = /demo-signIn.html

# 返回 類型設置(view 還是 json)
fantJ.security.browser.loginType = REDIRECT

總的來說钻注,SecurityProperties可以獲取到前綴為fantJ.security的所有屬性蚂且,BrowserProperties可以獲取到fantJ.security.browser下的所有屬性,所以BrowserProperties中會有對應的兩個字段loginPage 幅恋、loginType 杏死。
其中l(wèi)oginType 也是一個封裝枚舉類,特簡單的枚舉

package com.fantJ.core.properties;

/**
 * 登錄類型  枚舉類
 * Created by Fant.J.
 */
public enum LoginType {
    REDIRECT,
    JSON
}

  1. 配置都有了捆交,那接下來應該要寫如何去認證用戶淑翼。MyUserDetailsService里面可以用來獲取數(shù)據(jù)庫中的密碼然后打包返回用戶信息給security做用戶校驗使用,后者校驗如果與登錄的密碼match品追,如果成功玄括,返回UserDetail對象(用戶信息對象),進入自定義登錄成功后處理類MyAuthenticationSuccessHandler。如果失敗肉瓦,直接進入登錄失敗處理類MyAuthenticationFailHandler遭京。

MyUserDetailsService .java

package com.fantJ.browser;
/**
 * UserDetail 類
 * Created by Fant.J.
 */
@Component
public class MyUserDetailsService implements UserDetailsService {

//    @Autowired
//    private //在這里注入mapper,再想ia面根據(jù)用戶名做信息查找

    /**
     * 重寫PasswordEncoder  接口中的方法泞莉,實例化加密策略
     * @return 返回 BCrypt 加密策略
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private PasswordEncoder passwordEncoder;



    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 加載用戶數(shù)據(jù) , 返回UserDetail 實例
     * @param username  用戶登錄username
     * @return  返回User實體類 做用戶校驗
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("登錄用戶名:"+username);
        String password = passwordEncoder.encode("123456");
        //User三個參數(shù)   (用戶名+密碼+權限)
        //根據(jù)查找到的用戶信息判斷用戶是否被凍結
        logger.info("數(shù)據(jù)庫密碼:"+password);
        return new User(username,password,
                true,true,true,true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

    }
}

上面有段代碼

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

PasswordEncoder 是個接口哪雕,該接口下有兩個方法,一個是encoder 一個是matches鲫趁,前者用于加密斯嚎,后者用于匹配校驗,我們這里使用的是BCrypt加密算法來實現(xiàn)加密和匹配饮寞,所以在這里實現(xiàn)該接口的encoder方法孝扛,進行加密。

其次我想說的是重寫的loadUserByUsername方法幽崩,該方法將用戶登錄使用的username傳進來苦始,然后我們把該用戶的密碼從數(shù)據(jù)庫取出來,一同打包成User對象慌申,返回給security框架做下一步校驗陌选。其中User的幾個參數(shù)介紹見下面源碼:

    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
...
}

依次是:用戶名+密碼+可用?+沒過期蹄溉?+授權過期咨油?+不被鎖?+用戶權限(我在這里是手動加了個權限和密碼柒爵,自己根據(jù)業(yè)務修改下)

然后帶大家看下security內部是怎樣校驗用戶身份的役电。


首先是將身份加入權限列表中


省略了 很多個步驟,具體的大家可以自己打斷點 調試下棉胀。

  1. 最后我把成功/失敗處理器代碼貼出來
    MyAuthenticationSuccessHandler .java
package com.fantJ.browser.authentication;

/**
 * 自定義登錄成功處理類
 * Created by Fant.J.
 */
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    /**
     * 日志
     */
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * json 轉換工具類
     */
    private ObjectMapper objectMapper;
    @Autowired
    private SecurityProperties securityProperties;



    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("登錄成功");

        //判斷是json 格式返回 還是 view 格式返回
        if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            //將 authention 信息打包成json格式返回
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }else {
            //返回view
            super.onAuthenticationSuccess(request,response,authentication);
        }

    }
}

MyAuthenticationFailHandler .java

package com.fantJ.browser.authentication;

/**
 * 自定義登錄失敗處理器
 * Created by Fant.J.
 */
@Component
public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {

    /**
     * 日志
     */
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * json 轉換工具類
     */
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {

        logger.info("登錄失敗");

        //如果是json 格式
        if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            //設置狀態(tài)碼
            response.setStatus(500);
            //將 登錄失敗 信息打包成json格式返回
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(e));
        }else{
            //如果不是json格式法瑟,返回view
            super.onAuthenticationFailure(request,response,e);
        }

    }
}

他倆再哪里被調用了呢冀膝,再BrowserSecurityConfig類里,也就是security啟動核心配置類中霎挟,注入并


寫起來好麻煩窝剖,希望大家能看懂,有什么疑問的可以在下方留言酥夭。謝謝大家赐纱!

介紹下我的所有文集:

流行框架

SpringCloud
springboot
nginx
redis

底層實現(xiàn)原理:

Java NIO教程
Java reflection 反射詳解
Java并發(fā)學習筆錄
Java Servlet教程
jdbc組件詳解
Java NIO教程
Java語言/版本 研究

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市熬北,隨后出現(xiàn)的幾起案子疙描,更是在濱河造成了極大的恐慌,老刑警劉巖蒜埋,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淫痰,死亡現(xiàn)場離奇詭異,居然都是意外死亡整份,警方通過查閱死者的電腦和手機待错,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烈评,“玉大人火俄,你說我怎么就攤上這事〗补冢” “怎么了瓜客?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長竿开。 經(jīng)常有香客問我谱仪,道長,這世上最難降的妖魔是什么否彩? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任疯攒,我火速辦了婚禮,結果婚禮上列荔,老公的妹妹穿的比我還像新娘敬尺。我一直安慰自己,他們只是感情好贴浙,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布砂吞。 她就那樣靜靜地躺著,像睡著了一般崎溃。 火紅的嫁衣襯著肌膚如雪蜻直。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機與錄音袭蝗,去河邊找鬼唤殴。 笑死般婆,一個胖子當著我的面吹牛到腥,可吹牛的內容都是我干的。 我是一名探鬼主播蔚袍,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼乡范,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了啤咽?” 一聲冷哼從身側響起晋辆,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宇整,沒想到半個月后瓶佳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡鳞青,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年霸饲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片臂拓。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡厚脉,死狀恐怖,靈堂內的尸體忽然破棺而出胶惰,到底是詐尸還是另有隱情傻工,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布孵滞,位于F島的核電站中捆,受9級特大地震影響,放射性物質發(fā)生泄漏坊饶。R本人自食惡果不足惜泄伪,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幼东。 院中可真熱鬧臂容,春花似錦、人聲如沸根蟹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽简逮。三九已至球散,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間散庶,已是汗流浹背蕉堰。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工凌净, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屋讶。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓冰寻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親皿渗。 傳聞我的和親對象是個殘疾皇子斩芭,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內容