spring security(二)-自定義認(rèn)證(包含前后端分離認(rèn)證)

1.界面

  • 簡(jiǎn)介

    通過之前的章節(jié)實(shí)現(xiàn)了自定義登錄認(rèn)證,但是登錄的界面是框架提供的唉锌,有時(shí)候更希望是通過自定義登錄界面法瑟,接下來就來實(shí)現(xiàn)自定義登錄界面

  • 配置

    1. 復(fù)制spring-security-config-account項(xiàng)目钟病,修改名字為spring-security-login-page

    2. 修改啟動(dòng)類內(nèi)容如下

      package com.briup.security;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.annotation.Bean;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      import org.springframework.security.crypto.password.PasswordEncoder;
      
      @SpringBootApplication
      class SpringSecurityLoginPageApplication {
      
         public static void main(String[] args) {
             SpringApplication.run(SpringSecurityLoginPageApplication.class, args);
         }
      
      
         @Bean
         public PasswordEncoder passwordEncoder() {
             return new BCryptPasswordEncoder();
         }
      }
      
      
    3. 修改pom.xml文件蟹地,修改的內(nèi)容如下

      image-20210120102150914
    4. 測(cè)試

      訪問地址:http://127.0.0.1:9999/hello/test

      跳轉(zhuǎn)到默認(rèn)提供的登錄界面

      image-20210120102332475
  • 自定義登錄界面

    要想實(shí)現(xiàn)自定義登錄界面需要以下兩步:

    • 撰寫登錄界面
    • 修改security配置類

    接下來就來挨個(gè)實(shí)現(xiàn)這兩個(gè)步驟


    撰寫登錄界面

    src/mainresources新建static目錄贯莺,并且在該目錄下新建login.html,內(nèi)容如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登錄界面</title>
    </head>
    <body>
        <h1>登錄</h1>
        <!--
          請(qǐng)求方式必須為 post
      -->
        <form action="user/login" method="post">
            <!--
                name 屬性值必須為 username
            -->
            用戶名: <input type="text" name="username"><br>
    
    
            <!--
                password 屬性值必須為 password
            -->
            密碼: <input type="password" name="password"><br>
    
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    

    了解(start)

    表單的用戶名和密碼的name屬性值必須為usernamepassword,具體原因如下:

    • UsernamePasswordAuthenticationFilter中,獲取用戶名和密碼

    • 該過濾器獲取用戶名密碼則根據(jù)usernamepassword獲取柱衔,如下

      image-20210120103920614

      同時(shí)還限制了其請(qǐng)求方式為post

    了解(end)


    修改配置類

    找到配置類樊破,進(jìn)行修改,修改的內(nèi)容如下:

    package com.briup.security.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    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;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        @Qualifier("myDetailService")
        private UserDetailsService userDetailsService;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin() // 表示使用表單進(jìn)行登錄
                .loginPage("/login.html") // 表示登錄界面地址
                .loginProcessingUrl("/user/login") //表單登錄地址
                .and()  // 拼接 條件
                .authorizeRequests() // 設(shè)置需要認(rèn)證的請(qǐng)求地址
                .antMatchers("/","/login.html","/user/login").permitAll() // 設(shè)置 不需要認(rèn)證的請(qǐng)求
                .anyRequest().authenticated() // 任何請(qǐng)求都需要認(rèn)證
                .and()
                .csrf().disable(); // 關(guān)閉 security 的 csrf防護(hù)
    
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            /**
             * 設(shè)置認(rèn)證邏輯為用戶自定義認(rèn)證邏輯
             * 設(shè)置密碼加密處理器為 BCryptPasswordEncoder
             */
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        }
    }
    
    
  • 啟動(dòng)測(cè)試

    訪問地址: http://127.0.0.1:9999/test/hello

    image-20210120142355279

    從上圖可知愉棱,已經(jīng)跳轉(zhuǎn)到自定義登錄界面,輸入用戶名和密碼即可訪問哲戚,如下

    image-20210120144802243

    至此完成了自定義登錄界面

2.結(jié)果

2.1 實(shí)現(xiàn)

  • 簡(jiǎn)介

    從上述案例可知奔滑,當(dāng)訪問/test/hello,會(huì)跳轉(zhuǎn)到登錄界面顺少,登錄后在跳轉(zhuǎn)到/test/hello地址上朋其。

    但是當(dāng)直接訪問登錄界面進(jìn)行登錄時(shí),登錄后會(huì)直接跳轉(zhuǎn)到/請(qǐng)求脆炎,如下:

    image-20210120145212543

    同時(shí)當(dāng)?shù)卿浭r(shí)梅猿,頁面還是跳轉(zhuǎn)到登錄界面,只不過是地址發(fā)生了一點(diǎn)點(diǎn)變換秒裕,如下

    image-20210120145336138

    但實(shí)際開發(fā)過程中袱蚓,更加希望不管是登錄成功還是登錄失敗,都跳轉(zhuǎn)到開發(fā)人員指定的地址或者處理器進(jìn)行邏輯處理几蜻,接下來就來解決這個(gè)問題

  • 準(zhǔn)備工作

    1. 復(fù)制spring-security-login-page項(xiàng)目喇潘,修改名字為spring-security-login-result

    2. 修改pom.xml,修改部分如下圖

      image-20210120150557883
    3. 刪除.impl文件,讓其重新生成

    4. 將項(xiàng)目設(shè)置為maven項(xiàng)目

    5. 修改啟動(dòng)類名為SpringSecurityLoginResultApplication

  • 具體實(shí)現(xiàn)

    解決上述問題有兩種方式:

    • 通過配置登錄成功或者失敗后的地址處理
    • 通過配置登錄成功或者失敗后的處理器處理

    下面就對(duì)每種方案進(jìn)行實(shí)現(xiàn)


    自定義地址

    1. web層增加ResultController,內(nèi)容如下:

      package com.briup.security.web;
      
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      @RequestMapping("/result")
      public class ResultController {
      
          /**
           * 登錄成功跳轉(zhuǎn)地址
           * @return
           */
          @GetMapping("/success")
          public String loginSuccess() {
              return  "登錄成功";
          }
      
          /**
           * 登錄失敗跳轉(zhuǎn)地址
           * @return
           */
          @GetMapping("/fail")
          public String loginFail() {
              return  "登錄失敗";
          }
      }
      
      
    2. 修改配置梭稚,內(nèi)容如下:

      package com.briup.security.config;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      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;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.crypto.password.PasswordEncoder;
      
      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Autowired
          @Qualifier("myDetailService")
          private UserDetailsService userDetailsService;
      
          @Autowired
          private PasswordEncoder passwordEncoder;
      
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.formLogin() // 表示使用表單進(jìn)行登錄
                  .loginPage("/login.html") // 表示登錄界面地址
                  .loginProcessingUrl("/user/login") //表單登錄地址
                  .successForwardUrl("/result/success") // 登錄成功跳轉(zhuǎn)的地址
                  .failureForwardUrl("/result/fail")  // 登錄失敗跳轉(zhuǎn)的地址
                  .and()  // 拼接 條件
                  .authorizeRequests() // 設(shè)置需要認(rèn)證的請(qǐng)求地址
                  .antMatchers("/","/login.html","/user/login").permitAll() // 設(shè)置 不需要認(rèn)證的請(qǐng)求
                  .anyRequest().authenticated() // 任何請(qǐng)求都需要認(rèn)證
                  .and()
                  .csrf().disable(); // 關(guān)閉 security 的 csrf防護(hù)
      
          }
      
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              /**
               * 設(shè)置認(rèn)證邏輯為用戶自定義認(rèn)證邏輯
               * 設(shè)置密碼加密處理器為 BCryptPasswordEncoder
               */
              auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
          }
      }
      
      image-20210120152921249
    3. 啟動(dòng)測(cè)試

      當(dāng)輸入正確用戶名密碼時(shí)颖低,效果如下:

      image-20210120153018296

      當(dāng)輸入錯(cuò)誤的用戶名密碼,效果如下:


      image-20210120153047087

自定義處理器

  1. 新建登錄成功處理器,內(nèi)容如下:

    package com.briup.security.handler;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Collection;
    
    public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    
        /**
         * @param request   request對(duì)象
         * @param response  響應(yīng)對(duì)象
         * @param authentication 身份認(rèn)證對(duì)象弧烤,通過該對(duì)象可以獲取用戶名
         *
         *
         */
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            // 獲取用戶權(quán)限列表
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            authorities.forEach(System.out::println);
    
            // 獲取用戶名
            String name = authentication.getName();
            System.out.println(name);
    
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.print("登錄成功");
            writer.close();
        }
    }
    
    

    成功處理器必須要實(shí)現(xiàn)AuthenticationSuccessHandler

  2. 新建登錄失敗處理器,內(nèi)容如下:

    package com.briup.security.handler;
    
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    public class LoginFailHandler implements AuthenticationFailureHandler {
    
        /**
         * @param request 請(qǐng)求對(duì)象
         * @param response 響應(yīng)對(duì)象
         * @param exception 校驗(yàn)的異常對(duì)象
         */
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.print("登錄失敗:" + exception.getMessage());
            writer.close();
        }
    }
    
    

    失敗處理器必須要實(shí)現(xiàn)AuthenticationFailureHandler

  3. 修改配置類,內(nèi)容如下

    package com.briup.security.config;
    
    import com.briup.security.handler.LoginFailHandler;
    import com.briup.security.handler.LoginSuccessHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    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;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        @Qualifier("myDetailService")
        private UserDetailsService userDetailsService;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin() // 表示使用表單進(jìn)行登錄
                .loginPage("/login.html") // 表示登錄界面地址
                .loginProcessingUrl("/user/login") //表單登錄地址
                //.successForwardUrl("/result/success") // 登錄成功跳轉(zhuǎn)的地址
                //.failureForwardUrl("/result/fail")  // 登錄失敗跳轉(zhuǎn)的地址
                .successHandler(new LoginSuccessHandler())
                .failureHandler(new LoginFailHandler())
                .and()  // 拼接 條件
                .authorizeRequests() // 設(shè)置需要認(rèn)證的請(qǐng)求地址
                .antMatchers("/","/login.html","/user/login").permitAll() // 設(shè)置 不需要認(rèn)證的請(qǐng)求
                .anyRequest().authenticated() // 任何請(qǐng)求都需要認(rèn)證
                .and()
                .csrf().disable(); // 關(guān)閉 security 的 csrf防護(hù)
    
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            /**
             * 設(shè)置認(rèn)證邏輯為用戶自定義認(rèn)證邏輯
             * 設(shè)置密碼加密處理器為 BCryptPasswordEncoder
             */
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        }
    }
    
    

    與之前的配置類相比忱屑,修改了如下圖中內(nèi)容

    image-20210120155256684
    1. 啟動(dòng)測(cè)試

      輸入正確的用戶名密碼

      [圖片上傳失敗...(image-c8f7a0-1611217444363)]

      同時(shí)控制臺(tái)輸出內(nèi)容如下

      image-20210120155946228
  當(dāng)輸入錯(cuò)誤用戶名和密碼時(shí),如下:

  ![image-20210120160032809](https://upload-images.jianshu.io/upload_images/18110702-2ee34bf6afb24d1e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2.2 對(duì)比

  • 獲取權(quán)限方面

    通過地址進(jìn)行登錄的處理扼褪,無法獲取到到權(quán)限

    通過處理器進(jìn)行登錄處理想幻,可以獲取到用戶名

  • 獲取用戶名方面

    通過地址進(jìn)行登錄處理,直接在方法上注入用戶名密碼即可

    image-20210120163054617

    通過處理器進(jìn)行登錄處理话浇,則需要借助于Authentication實(shí)例

3.分離

  • 簡(jiǎn)介

    在前后端分離情況下脏毯,登錄邏輯與之前的登錄邏輯不通,具體如下:

    • 后端不需要提供界面幔崖,而是提供一個(gè)登錄接口食店,登錄成功以后產(chǎn)生一個(gè)token,返回給前端
    • 前端在之后的請(qǐng)求將產(chǎn)生的token赏寇,發(fā)送給后端吉嫩,后端進(jìn)行校驗(yàn),校驗(yàn)通過認(rèn)為登錄通過嗅定,讓其訪問具體的資源

    具體如下圖所示:

    image-20210120170827558
  • 實(shí)現(xiàn)

    實(shí)現(xiàn)基于Spring Security的前后端分離登錄需要以下幾個(gè)步驟

    • 項(xiàng)目準(zhǔn)備
    • 增加swagger
    • 增加jwt
    • 增加自定義響應(yīng)結(jié)構(gòu)
    • 增加處理邏輯
  • 增加校驗(yàn)邏輯

接下來就挨個(gè)實(shí)現(xiàn)上述步驟


3.1 項(xiàng)目準(zhǔn)備

  1. 復(fù)制spring-security-config-account項(xiàng)目自娩,修改名字為spring-security-separate-login

  2. 修改pom.xml,修改后變換的內(nèi)容如下標(biāo)注

    image-20210120171635669

    同時(shí)刪除<name>標(biāo)簽中的內(nèi)容

  3. 刪除.impl文件,讓其重新生成

  4. 將復(fù)制的內(nèi)容是其稱為一個(gè)maven項(xiàng)目

  5. 將項(xiàng)目clean

  6. 修改啟動(dòng)類名為SpringSecuritySeparateLoginApplication

3.2 接口文檔

swagger 作用這里不再解釋渠退,可以參考其他資料了解swagger的作用

修改pom.xml,增加swagger配置

<dependency>
  <groupId>com.spring4all</groupId>
  <artifactId>swagger-spring-boot-starter</artifactId>
  <version>1.9.0.RELEASE</version>
</dependency>

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

修改配置文件application.yml,增加swagger配置

server:
  port: 9999
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://172.16.0.154:3306/test?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
  jpa:
    show-sql: true
swagger:
  base-package: com.briup.security.web

在啟動(dòng)類上加上@EnableSwagger2Doc注解

image-20210121144725899

3.3 JWT配置

jwt 作用這里不再解釋忙迁,可以參考其他資料了解jwt的作用

修改pom.xml,增加jwt依賴

  <dependency>
    <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
    <version>3.11.0</version>
  </dependency>

增加jwt工具類脐彩,內(nèi)容如下

package com.briup.security.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
* @author wangzh
*/
public class JwtUtil {

  /**
   * 過期時(shí)間 單位:毫秒
   */
  private static final long EXPIRE_TIME =   30 * 60 * 1000;

  private static final String SECRET = "security_jwt";

  public static final String TOKEN_HEAD = "TOKEN";

  private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);

  /**
   * 簽發(fā) token
   * @param userId 用戶信息
   * @param info 用戶自定義信息
   * @return
   */
  public static String sign(String userId, Map<String,Object> info) {
      // 設(shè)置過期時(shí)間
      Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
      // 設(shè)置加密算法
      Algorithm hmac512 = Algorithm.HMAC512(SECRET);
      return JWT.create()
                  .withAudience(userId) // 將用戶id放入到token中
                  .withClaim("info",info) // 自定義用戶信息
                  .withExpiresAt(date) // 設(shè)置過期時(shí)間
                  .sign(hmac512);

  }

  /**
   * 從token中獲取userId
   * @param token
   * @return
   */
  public static String getUserId(String token) {
      try {
          return JWT.decode(token).getAudience().get(0);
      } catch (JWTDecodeException e) {
          logger.error(e.getMessage());
          return null;
      }
  }

  /**
   * 從token中獲取自定義信息
   * @param token
   * @return
   */
  public static Map<String,Object> getInfo(String token) {
      try {
          Claim claim = JWT.decode(token).getClaim("info");
          return claim.asMap();
      } catch (JWTDecodeException e) {
          logger.error(e.getMessage());
          return null;
      }
  }

  /**
   * 校驗(yàn)token
   * @param token
   * @return
   */
  public static boolean checkSign(String token) {
      try {
          Algorithm algorithm = Algorithm.HMAC512(SECRET);
          JWTVerifier verifier = JWT.require(algorithm).build();
          verifier.verify(token);
          return true;
      } catch (Exception e) {
          logger.info("token 無效:" + e.getMessage());
          throw new RuntimeException("token無效,請(qǐng)重新獲取");
      }

  }



}

3.4 響應(yīng)結(jié)構(gòu)

前后端分離中姊扔,增加自定義響應(yīng)結(jié)構(gòu)惠奸,這樣可以更加規(guī)范后端返回給前端的數(shù)據(jù)樣式

修改pom.xml,增加lombok依賴

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

增加自定義響應(yīng)結(jié)構(gòu)類

package com.briup.security.util;

import lombok.Data;
import lombok.Getter;

@Getter
public class Result<T> {
    /**
     * 業(yè)務(wù)狀態(tài)碼
     */
    private Integer code;
    /**
     * 狀態(tài)碼信息對(duì)應(yīng)數(shù)據(jù)
     */
    private String message;

    /**
     * 響應(yīng)時(shí)間
     */
    private Long time;

    /**
     * 響應(yīng)數(shù)據(jù)
     */
    private T data;

    private Result(Integer code,String message,T data) {
        this.code = code;
        this.message = message;
        this.time = System.currentTimeMillis();
        this.data = data;
    }

    public static <E> Result<E> success(E data) {
        return new Result<>(200,"成功",data);
    }


    public static  Result success() {
        return success(null);
    }

    public static <E> Result<E> fail(Integer code,String message,E data) {
        return new Result<>(code,message,data);
    }

   public static Result fail(Integer code,String message) {
       return new Result<>(code, message, null);
   }
}

3.5 結(jié)果處理

這里演示在處理器 和 url返回token

3.5.1 處理器處理

  1. 登錄成功以后,在處理器返回token

    增加登錄controller

    package com.briup.security.web;
    
    import io.swagger.annotations.Api;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @Api(tags = "賬戶管理")
    @RequestMapping("/account")
    public class AccountController {
    
        @PostMapping("/login")
        public void login(String username,String password) {
        }
    }
    
    

    注意:這里不要寫任何邏輯,spring security自己會(huì)去校驗(yàn)用戶名和密碼

    新增成功處理器恰梢,內(nèi)容如下

    package com.briup.security.handler;
    
    import com.briup.security.util.JwtUtil;
    import com.briup.security.util.Result;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    public class SuccessHandler implements AuthenticationSuccessHandler {
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            // 登錄成功產(chǎn)生token
            String name = authentication.getName();
            String token = JwtUtil.sign(name, null);
            String result = objectMapper.writeValueAsString(Result.success(token));
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(result);
            writer.close();
        }
    }
    

    新增失敗處理器

    package com.briup.security.handler;
    
    import com.briup.security.util.Result;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    public class FailHandler implements AuthenticationFailureHandler {
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            String result = objectMapper.writeValueAsString(Result.fail(501, "用戶名密碼錯(cuò)誤"));
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(result);
            writer.close();
        }
    }
    
    

    修改配置類佛南,內(nèi)容如下:

    package com.briup.security.config;
    
    import com.briup.security.handler.FailHandler;
    import com.briup.security.handler.SuccessHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    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;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        @Qualifier("myDetailService")
        private UserDetailsService userDetailsService;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Bean
        public SuccessHandler successHandler() {
            return new SuccessHandler();
        }
    
        @Bean
        public FailHandler failHandler() {
            return new FailHandler();
        }
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()
                .loginPage("/account/page") // 當(dāng)請(qǐng)求需要認(rèn)證,跳轉(zhuǎn)到該地址
                .loginProcessingUrl("/account/login") // 請(qǐng)求認(rèn)證地址
                .successHandler(successHandler())
                .failureHandler(failHandler())
                .and()
                .authorizeRequests()
                .antMatchers("/account/login","/account/page").permitAll() // 登錄請(qǐng)求也不需要認(rèn)證
                .antMatchers(
                            "/webjars/**",
                            "/api/**",
                            "/swagger-ui.html",
                            "/swagger-resources/**",
                            "/v2/**",
                            "/swagger-resources/**").permitAll() // swagger 界面不需要認(rèn)證
                .anyRequest().authenticated()
                .and().csrf().disable();
    
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            /**
             * 設(shè)置認(rèn)證邏輯為用戶自定義認(rèn)證邏輯
             * 設(shè)置密碼加密處理器為 BCryptPasswordEncoder
             */
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        }
    }
    

    啟動(dòng)測(cè)試

    image-20210121145252135

    訪問登錄請(qǐng)求,可以看到返回token地址

    image-20210121145342810

    同時(shí)用戶名輸入錯(cuò)誤嵌言,也可以看到對(duì)應(yīng)的結(jié)果

    image-20210121145414947

    訪問其他請(qǐng)求嗅回,發(fā)現(xiàn)也訪問不了

    image-20210121145501945

    至此,完成了登錄成功后在處理器返回token

3.5.2 URL處理

登錄成功后通過url返回token

增加登錄成功或者失敗以后的接口

package com.briup.security.web;

import com.briup.security.util.JwtUtil;
import com.briup.security.util.Result;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Api(tags = "賬戶管理")
@RequestMapping("/account")
public class AccountController {

    @PostMapping("/login")
    public void login(String username,String password) {
    }

    @GetMapping("/page")
    public Result<String> page() {
        return Result.fail(401,"該請(qǐng)求需要認(rèn)證");
    }

    @PostMapping("/success")
    public Result<String> success(String username) {
        return Result.success(JwtUtil.sign(username,null));
    }


    @PostMapping("/fail")
    public Result<String> fail(String username) {
        return Result.fail(401,"用戶名密碼錯(cuò)誤");
    }
}

修改配置類呀页,內(nèi)容如下

package com.briup.security.config;

import com.briup.security.handler.FailHandler;
import com.briup.security.handler.SuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("myDetailService")
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    public SuccessHandler successHandler() {
        return new SuccessHandler();
    }

    @Bean
    public FailHandler failHandler() {
        return new FailHandler();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/account/page") // 當(dāng)請(qǐng)求需要認(rèn)證,跳轉(zhuǎn)到該地址
            .loginProcessingUrl("/account/login") // 請(qǐng)求認(rèn)證地址
           // .successHandler(successHandler())
           // .failureHandler(failHandler())
            .successForwardUrl("/account/success")
            .failureForwardUrl("/account/fail")
            .and()
            .authorizeRequests()
            .antMatchers("/account/login","/account/page").permitAll() // 登錄請(qǐng)求也不需要認(rèn)證
            .antMatchers(
                        "/webjars/**",
                        "/api/**",
                        "/swagger-ui.html",
                        "/swagger-resources/**",
                        "/v2/**",
                        "/swagger-resources/**").permitAll() // swagger 界面不需要認(rèn)證
            .anyRequest().authenticated()
            .and().csrf().disable();

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * 設(shè)置認(rèn)證邏輯為用戶自定義認(rèn)證邏輯
         * 設(shè)置密碼加密處理器為 BCryptPasswordEncoder
         */
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }
}

效果與之前效果一樣妈拌,這里就不做演示了

3.6 TOKEN校驗(yàn)

認(rèn)證完成,則需要校驗(yàn)token蓬蝶,校驗(yàn)token其原理很簡(jiǎn)單,具體如下:

  • UsernamePasswordAuthenticationFilter過濾器執(zhí)行之前增加一個(gè)自定義過濾器
  • 自定義過濾器就是用來校驗(yàn)token,token合法則將請(qǐng)求轉(zhuǎn)發(fā)給下一個(gè)過濾器

接下來就來實(shí)現(xiàn)上述過程猜惋,具體如下

  1. 自定義過濾器

    package com.briup.security.filter;
    
    import com.briup.security.util.JwtUtil;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class AuthenticationTokenFilter extends OncePerRequestFilter {
    
        private static final Logger logger = LoggerFactory.getLogger(AuthenticationTokenFilter.class);
    
        @Autowired
        @Qualifier("myDetailService")
        private UserDetailsService userDetailsService;
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // 1.從請(qǐng)求頭中獲取Token
            String token = request.getHeader(JwtUtil.TOKEN_HEAD);
    
            //2.判斷token是否為空,則請(qǐng)求放心丸氛,讓UsernamePasswordAuthenticationFilter校驗(yàn)用戶名密碼
            if (token == null || "".equals(token)) {
                filterChain.doFilter(request,response);
                return;
            }
    
            try {
                //3.如果token不為空,則去校驗(yàn)token,
                if (JwtUtil.checkSign(token)) {
                    // 獲取用戶信息
                    String userId = JwtUtil.getUserId(token);
                    UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
                    /**
                     *  UsernamePasswordAuthenticationToken
                     *      這個(gè)對(duì)象使用來保存用戶信息
                     *  如果SecurityContextHolder.getContext()中有該對(duì)象著摔,那么就不需要再次校驗(yàn)
                     */
                    UsernamePasswordAuthenticationToken authenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            } catch (Exception e) {
                e.printStackTrace();
                logger.info("校驗(yàn)用戶名密碼失敗");
            }
        }
    }
    
  2. 修改配置類缓窜,將上述過濾器添加到UsernamePasswordAuthenticationFilter前面

    package com.briup.security.config;
    
    import com.briup.security.filter.AuthenticationTokenFilter;
    import com.briup.security.handler.FailHandler;
    import com.briup.security.handler.SuccessHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    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;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        @Qualifier("myDetailService")
        private UserDetailsService userDetailsService;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Bean
        public SuccessHandler successHandler() {
            return new SuccessHandler();
        }
    
        @Bean
        public FailHandler failHandler() {
            return new FailHandler();
        }
    
    
        @Bean
        public AuthenticationTokenFilter authenticationTokenFilter() {
            return new AuthenticationTokenFilter();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); // 添加過濾器到 UsernamePasswordAuthenticationFilter前面
            
            http.formLogin()
                .loginPage("/account/page") // 當(dāng)請(qǐng)求需要認(rèn)證,跳轉(zhuǎn)到該地址
                .loginProcessingUrl("/account/login") // 請(qǐng)求認(rèn)證地址
                .successHandler(successHandler())
                .failureHandler(failHandler())
               // .successForwardUrl("/account/success")
               // .failureForwardUrl("/account/fail")
                .and()
                .authorizeRequests()
                .antMatchers("/account/login","/account/page").permitAll() // 登錄請(qǐng)求也不需要認(rèn)證
                .antMatchers(
                            "/webjars/**",
                            "/api/**",
                            "/swagger-ui.html",
                            "/swagger-resources/**",
                            "/v2/**",
                            "/swagger-resources/**").permitAll() // swagger 界面不需要認(rèn)證
                .anyRequest().authenticated()
                .and().csrf().disable();
    
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            /**
             * 設(shè)置認(rèn)證邏輯為用戶自定義認(rèn)證邏輯
             * 設(shè)置密碼加密處理器為 BCryptPasswordEncoder
             */
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        }
    }
    
    
  3. 啟動(dòng)測(cè)試

    進(jìn)行登錄產(chǎn)生token

    image-20210121160944699

    將token添加到swagger認(rèn)證中

    image-20210121161013058

    訪問test/hello請(qǐng)求

    image-20210121161036582

    說明過濾器生效,且校驗(yàn)通過

4.地址

代碼地址:https://gitee.com/wangzh991122/security.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載谍咆,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者禾锤。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市摹察,隨后出現(xiàn)的幾起案子恩掷,更是在濱河造成了極大的恐慌,老刑警劉巖供嚎,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄娘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡克滴,警方通過查閱死者的電腦和手機(jī)逼争,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劝赔,“玉大人誓焦,你說我怎么就攤上這事∽琶保” “怎么了杂伟?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵移层,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我稿壁,道長(zhǎng)幽钢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任傅是,我火速辦了婚禮匪燕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喧笔。我一直安慰自己帽驯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布书闸。 她就那樣靜靜地躺著尼变,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浆劲。 梳的紋絲不亂的頭發(fā)上嫌术,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音牌借,去河邊找鬼度气。 笑死,一個(gè)胖子當(dāng)著我的面吹牛膨报,可吹牛的內(nèi)容都是我干的磷籍。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼现柠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼院领!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起够吩,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤比然,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后废恋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谈秫,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年鱼鼓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拟烫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迄本,死狀恐怖硕淑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤置媳,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布于樟,位于F島的核電站,受9級(jí)特大地震影響拇囊,放射性物質(zhì)發(fā)生泄漏迂曲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一寥袭、第九天 我趴在偏房一處隱蔽的房頂上張望路捧。 院中可真熱鬧,春花似錦传黄、人聲如沸杰扫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽章姓。三九已至,卻和暖如春识埋,著一層夾襖步出監(jiān)牢的瞬間凡伊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工窒舟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窗声,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓辜纲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拦耐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耕腾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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