1.界面
-
簡(jiǎn)介
通過之前的章節(jié)實(shí)現(xiàn)了自定義登錄認(rèn)證,但是登錄的界面是框架提供的唉锌,有時(shí)候更希望是通過自定義登錄界面法瑟,接下來就來實(shí)現(xiàn)自定義登錄界面
-
配置
復(fù)制
spring-security-config-account
項(xiàng)目钟病,修改名字為spring-security-login-page
-
修改啟動(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(); } }
-
修改
pom.xml
文件蟹地,修改的內(nèi)容如下image-20210120102150914 -
測(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
屬性值必須為username
和password
,具體原因如下:UsernamePasswordAuthenticationFilter
中,獲取用戶名和密碼-
該過濾器獲取用戶名密碼則根據(jù)
username
和password
獲取柱衔,如下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)備工作
復(fù)制
spring-security-login-page
項(xiàng)目喇潘,修改名字為spring-security-login-result
-
修改
pom.xml
,修改部分如下圖image-20210120150557883 刪除
.impl
文件,讓其重新生成將項(xiàng)目設(shè)置為
maven
項(xiàng)目修改啟動(dòng)類名為
SpringSecurityLoginResultApplication
-
具體實(shí)現(xiàn)
解決上述問題有兩種方式:
- 通過配置登錄成功或者失敗后的地址處理
- 通過配置登錄成功或者失敗后的處理器處理
下面就對(duì)每種方案進(jìn)行實(shí)現(xiàn)
自定義地址
-
在
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 "登錄失敗"; } }
-
修改配置梭稚,內(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 -
啟動(dòng)測(cè)試
當(dāng)輸入正確用戶名密碼時(shí)颖低,效果如下:
image-20210120153018296當(dāng)輸入錯(cuò)誤的用戶名密碼,效果如下:
image-20210120153047087
自定義處理器
-
新建登錄成功處理器,內(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
-
新建登錄失敗處理器,內(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
-
修改配置類,內(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-
啟動(dòng)測(cè)試
輸入正確的用戶名密碼
[圖片上傳失敗...(image-c8f7a0-1611217444363)]
同時(shí)控制臺(tái)輸出內(nèi)容如下
image-20210120155946228
-
當(dāng)輸入錯(cuò)誤用戶名和密碼時(shí),如下:

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)備
復(fù)制
spring-security-config-account
項(xiàng)目自娩,修改名字為spring-security-separate-login
-
修改
pom.xml
,修改后變換的內(nèi)容如下標(biāo)注image-20210120171635669同時(shí)刪除
<name>
標(biāo)簽中的內(nèi)容 刪除
.impl
文件,讓其重新生成將復(fù)制的內(nèi)容是其稱為一個(gè)maven項(xiàng)目
將項(xiàng)目clean
修改啟動(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
注解
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 處理器處理
-
登錄成功以后,在處理器返回
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)上述過程猜惋,具體如下
-
自定義過濾器
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)用戶名密碼失敗"); } } }
-
修改配置類缓窜,將上述過濾器添加到
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); } }
-
啟動(dòng)測(cè)試
進(jìn)行登錄產(chǎn)生
token
image-20210121160944699將token添加到
swagger
認(rèn)證中image-20210121161013058訪問
test/hello
請(qǐng)求image-20210121161036582說明過濾器生效,且校驗(yàn)通過