springboot+springsecurity實現前後端分離簡單實現!

1、前言部分

1。1、如何學習?

看springsecurtiy原理圖的時候以為灑灑水,結果自己動手做的時候一竅不通,所以一定不要眼高手低,實踐出真知!透過各種方式學習springsecurity,在B站、騰訊課堂、網易課堂、慕課網沒有springsecurity的前後端分離的教學影片,那我就去csdn去尋找springsecurity部落格,發現幾個問題:

要麼就是前後端不分離,要麼就是透過記憶體方式讀取資料,而不是透過資料庫的方式讀取資料,要麼就是大佬們給的程式碼不全、把程式碼講的太繞,關鍵部分沒有註釋。

講的例子不那麼通俗易懂,不利於新手的學習。

程式碼本身有bug,或者就沒有我想要實現的效果。

實在不行我又跑去github上找開源專案學習,github由於是外國網站,國內訪問速度有點慢!!那就用國內的gitee吧,gitee上的開源專案都是結合實戰專案的,程式碼邏輯也比較複雜,我對專案的業務邏輯沒什麼瞭解,感覺不適合我。我這一次選擇比較反人性的方式去學習,就是手撕原始碼和看官方文件。老實講,剛開始看原始碼和官方文件特別難受,並且看不進去,那些springsecurity的類還有介面名字又臭又長,這時我就下載原始碼,原始碼的註釋多的就像一本書,非常詳細且權威。

當然別指望看幾遍就能看懂,我看這些註釋、原始碼、部落格看了10幾遍甚至20幾遍才看懂,每次去看都有不同的收穫!!!

此文章截圖水平不高、理解為主、欣賞為輔!!內容有點多,每一步都有詳細解析,請耐心看完,看不懂可以多看幾遍。。

1。2、技術支援

jdk 1。8、springboot 2。3。4、mybatis-plus 3。4。1、mysql 5。5、springsecurity 5。3。4、springmvc、lombok簡化entity程式碼,不用你去寫get、set方法,全部自動生成、gson 2。8。2 將json物件轉化成json字串

1。3、預期實現效果圖

未登入時訪問指定資源, 返回未登入的json字串 , index是我在controller層寫的一個簡單介面,返回index字串

springboot+springsecurity實現前後端分離簡單實現!

輸入賬號錯誤,返回使用者名稱錯誤的json字串 , 需說明一點,/login是springsecurity封裝好的介面,無須你在controller寫login介面,/logout也同理。

springboot+springsecurity實現前後端分離簡單實現!

輸入密碼錯誤,返回密碼錯誤的json字串

springboot+springsecurity實現前後端分離簡單實現!

登入成功, 返回登入成功的json字串並返回cookie

springboot+springsecurity實現前後端分離簡單實現!

登入成功並且擁有許可權訪問指定資源, 返回資源相關資料的json字串

springboot+springsecurity實現前後端分離簡單實現!

springboot+springsecurity實現前後端分離簡單實現!

springboot+springsecurity實現前後端分離簡單實現!

登入成功但無許可權訪問指定資源時,返回許可權不足的json字串

springboot+springsecurity實現前後端分離簡單實現!

異地登入,返回異地登入,強制下線的json字串 , 測試的基礎是要在兩臺不同的機器上登入,然後訪問/index。

springboot+springsecurity實現前後端分離簡單實現!

登出成功,返回登出成功的json字串並刪除cookie

springboot+springsecurity實現前後端分離簡單實現!

2、核心部分

2。1、springsecurity原理解釋:

springsecurity最重要的兩個部分: authentication(認證) 和 authorization(授權)

認證: 就是判定你是什麼身份,管理員還是普通人

授權: 什麼樣的身份擁有什麼樣的權利。

springboot+springsecurity實現前後端分離簡單實現!

springboot+springsecurity實現前後端分離簡單實現!

簡單理解: 自定義配置登入成功、登陸失敗、登出成功目標結果類,並將其注入到springsecurity的配置檔案中。如何認證、授權交給AuthenticationManager去作

複雜理解:

(1)使用者發起表單登入請求後,首先進入

UsernamePasswordAuthenticationFilter

, 在 UsernamePasswordAuthenticationFilter中根據使用者輸入的使用者名稱、密碼構建了

UsernamePasswordAuthenticationToken

,並將其交給 AuthenticationManager 來進行認證處理。

springboot+springsecurity實現前後端分離簡單實現!

AuthenticationManager 本身不包含認證邏輯,其核心是用來管理所有的

AuthenticationProvider

,透過交由合適的 AuthenticationProvider 來實現認證。

(2)下面跳轉到了

SelfAuthenticationProvider

,該類是 AuthenticationProvider 的實現類:你可以在該類的

Authentication authenticate(Authentication authentication)

自定義認證邏輯, 然後在該類中透過呼叫

UserDetails loadUserByUsername(account)

去獲取資料庫使用者資訊並驗證,然後建立

並將許可權、使用者個人資訊注入到其中 ,並透過

setAuthenticated(true)

設定為需要驗證。

springboot+springsecurity實現前後端分離簡單實現!

(3) 至此認證資訊就被傳遞迴 UsernamePasswordAuthenticationFilter 中,在 UsernamePasswordAuthenticationFilter 的父類

AbstractAuthenticationProcessingFilter

doFilter()

中,會根據認證的成功或者失敗呼叫相應的 handler:所謂的handler就是我們注入到springsecurity配置檔案的handler。

2。2、踩坑集錦

訪問/login時必須要用post方法!, 訪問的引數名必須為username和password

訪問/logout時即可用post也可用get方法!

//springsecurity配置檔案中的hasRole(“”)不能以ROLE開頭,比如ROLE_USER就是錯的,springsecurity會預設幫我們加上,但資料庫的許可權欄位必須是ROLE_開頭,否則讀取不到

。antMatchers(“/index”)。hasRole(“USER”)

。antMatchers(“/hello”)。hasRole(“ADMIN”)

2。3、程式碼部分

pom依賴檔案

<!——轉換成json字串的工具——>

com。google。code。gson

gson

2。8。2

<!——springboot整合web操作7——>

org。springframework。boot

spring-boot-starter-web

<!——springsecurity——>

org。springframework。boot

spring-boot-starter-security

<!——mysql驅動——>

mysql

mysql-connector-java

runtime

<!——lombok依賴——>

org。projectlombok

lombok

true

<!——mybatis-plus依賴——>

com。baomidou

mybatis-plus-boot-starter

3。4。1

<!——springboot-自帶測試工具——>

org。springframework。boot

spring-boot-starter-test

test

org。junit。vintage

junit-vintage-engine

org。springframework。security

spring-security-config

5。3。4。RELEASE

org。springframework。security

spring-security-web

5。3。4。RELEASE

Msg。java 自定義返回結果集,這個看個人的,怎麼開心怎麼來!

@Data

@NoArgsConstructor

@AllArgsConstructor

public class Msg {

int code; //錯誤碼

String Message; //訊息提示

Map data=new HashMap(); //資料

//無權訪問

public static Msg denyAccess(String message){

Msg result=new Msg();

result。setCode(300);

result。setMessage(message);

return result;

}

//操作成功

public static Msg success(String message){

Msg result=new Msg();

result。setCode(200);

result。setMessage(message);

return result;

}

//客戶端操作失敗

public static Msg fail(String message){

Msg result=new Msg();

result。setCode(400);

result。setMessage(message);

return result;

}

public Msg add(String key,Object value){

this。data。put(key,value);

return this;

}

}

User。java ,此類是entity實體類

@Data

public class User implements Serializable {

private Integer id;

private String account;

private String password;

private String role;

}

UserMapper。java ,此介面繼承 BaseMapper類,而BaseMapper類 封裝了大量的sql,極大程度簡化了程式設計師sql語句的書寫。

@Repository

public interface UserMapper extends BaseMapper {

}

正常情況下要寫UserService。java介面,但是此文章只是用於演示效果,就沒書寫了

public interface UserService{

}

UserServiceImpl。java,使其實現 UserDetailsService介面, 從而去獲取資料庫使用者資訊,詳細解析請看註釋部分。

@Service

public class UserServiceImpl extends ServiceImpl implements UserService,UserDetailsService {

@Autowired

UserMapper userMapper;

//載入使用者

@Override

public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

//mybatis-plus幫我們寫好了sql語句,相當於 select * from user where account =‘${account}’

QueryWrapper wrapper=new QueryWrapper<>();

wrapper。eq(“account”,s);

User user=userMapper。selectOne(wrapper); //user即為查詢結果

if(user==null){

throw new UsernameNotFoundException(“使用者名稱錯誤!!”);

}

//獲取使用者許可權,並把其新增到GrantedAuthority中

List grantedAuthorities=new ArrayList<>();

GrantedAuthority grantedAuthority=new SimpleGrantedAuthority(user。getRole());

grantedAuthorities。add(grantedAuthority);

//方法的返回值要求返回UserDetails這個資料型別, UserDetails是介面,找它的實現類就好了

//new org。springframework。security。core。userdetails。User(String username,String password,Collection<? extends GrantedAuthority> authorities) 就是它的實現類

return new org。springframework。security。core。userdetails。User(s,user。getPassword(),grantedAuthorities);

}

}

UserController。java 就是普通的controller

@RestController

public class UserController {

@GetMapping(“index”)

public String index(){

return “index”;

}

@GetMapping(“hello”)

public String hello(){

return “hello”;

}

}

AuthenticationEnryPoint 。java 自定義未登入的處理邏輯

@Component

public class AuthenticationEnryPoint implements AuthenticationEntryPoint {

@Autowired

Gson gson;

//未登入時返回給前端資料

@Override

public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {

Msg result=Msg。fail(“需要登入!!”);

response。setContentType(“application/json;charset=utf-8”);

response。getWriter()。write(gson。toJson(result));

}

}

AuthenticationFailure。java 自定義登入失敗時的處理邏輯

//登入失敗返回給前端訊息

@Component

public class AuthenticationFailure implements AuthenticationFailureHandler{

@Autowired

Gson gson;

@Override

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

Msg msg=null;

if(e instanceof UsernameNotFoundException){

msg=Msg。fail(e。getMessage());

}else if(e instanceof BadCredentialsException){

msg=Msg。fail(“密碼錯誤!!”);

}else {

msg=Msg。fail(e。getMessage());

}

//處理編碼方式,防止中文亂碼的情況

response。setContentType(“text/json;charset=utf-8”);

//返回給前臺

response。getWriter()。write(gson。toJson(msg));

}

}

AuthenticationSuccess。java 自定義登入成功時的處理邏輯

@Component

public class AuthenticationSuccess implements AuthenticationSuccessHandler{

@Autowired

Gson gson;

@Override

public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

//登入成功時返回給前端的資料

Msg result=Msg。success(“登入成功!!!!!”);

response。setContentType(“application/json;charset=utf-8”);

response。getWriter()。write(gson。toJson(result));

}

}

AuthenticationLogout。java 自定義登出時的處理邏輯

@Component

public class AuthenticationLogout implements LogoutSuccessHandler{

@Autowired

Gson gson;

@Override

public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

Msg result=Msg。success(“登出成功”);

response。setContentType(“application/json;charset=utf-8”);

response。getWriter()。write(gson。toJson(result));

}

}

AccessDeny。java 自定義無許可權訪問時的邏輯處理

//無權訪問

@Component

public class AccessDeny implements AccessDeniedHandler{

@Autowired

Gson gson;

@Override

public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {

Msg result= Msg。denyAccess(“無權訪問,need Authorities!!”);

response。setContentType(“application/json;charset=utf-8”);

response。getWriter()。write(gson。toJson(result));

}

}

SessionInformationExpiredStrategy。java 自定義異地登入、賬號下線時的邏輯處理

@Component

public class SessionInformationExpiredStrategy implements org。springframework。security。web。session。SessionInformationExpiredStrategy{

@Autowired

Gson gson;

@Override

public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {

Msg result= Msg。fail(“您的賬號在異地登入,建議修改密碼”);

HttpServletResponse response=event。getResponse();

response。setContentType(“application/json;charset=utf-8”);

response。getWriter()。write(gson。toJson(result));

}

}

SelfAuthenticationProvider。java 自定義認證邏輯處理

@Component

public class SelfAuthenticationProvider implements AuthenticationProvider{

@Autowired

UserServiceImpl userServiceImpl;

@Autowired

BCryptPasswordEncoder bCryptPasswordEncoder;

@Override

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

String account= authentication。getName(); //獲取使用者名稱

String password= (String) authentication。getCredentials(); //獲取密碼

UserDetails userDetails= userServiceImpl。loadUserByUsername(account);

boolean checkPassword= bCryptPasswordEncoder。matches(password,userDetails。getPassword());

if(!checkPassword){

throw new BadCredentialsException(“密碼不正確,請重新登入!”);

}

return new UsernamePasswordAuthenticationToken(account,password,userDetails。getAuthorities());

}

@Override

public boolean supports(Class<?> aClass) {

return true;

}

}

SpringsecurityConfig。java是springsecurity的配置,詳細解析請看註釋!!

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true) //開啟許可權註解,預設是關閉的

public class SpringsecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired

AuthenticationEnryPoint authenticationEnryPoint; //未登入

@Autowired

AuthenticationSuccess authenticationSuccess; //登入成功

@Autowired

AuthenticationFailure authenticationFailure; //登入失敗

@Autowired

AuthenticationLogout authenticationLogout; //登出

@Autowired

AccessDeny accessDeny; //無權訪問

@Autowired

SessionInformationExpiredStrategy sessionInformationExpiredStrategy; //檢測異地登入

@Autowired

SelfAuthenticationProvider selfAuthenticationProvider; //自定義認證邏輯處理

@Bean

public UserDetailsService userDetailsService() {

return new UserServiceImpl();

}

//加密方式

@Bean

public BCryptPasswordEncoder bCryptPasswordEncoder() {

return new BCryptPasswordEncoder();

}

//認證

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth。authenticationProvider(selfAuthenticationProvider);

}

//授權

@Override

protected void configure(HttpSecurity http) throws Exception {

//cors()解決跨域問題,csrf()會與restful風格衝突,預設springsecurity是開啟的,所以要disable()關閉一下

http。cors()。and()。csrf()。disable();

// /index需要許可權為ROLE_USER才能訪問 /hello需要許可權為ROLE_ADMIN才能訪問

http。authorizeRequests()

。antMatchers(“/index”)。hasRole(“USER”)

。antMatchers(“/hello”)。hasRole(“ADMIN”)

。and()

。formLogin() //開啟登入

。permitAll() //允許所有人訪問

。successHandler(authenticationSuccess) // 登入成功邏輯處理

。failureHandler(authenticationFailure) // 登入失敗邏輯處理

。and()

。logout() //開啟登出

。permitAll() //允許所有人訪問

。logoutSuccessHandler(authenticationLogout) //登出邏輯處理

。deleteCookies(“JSESSIONID”) //刪除cookie

。and()。exceptionHandling()

。accessDeniedHandler(accessDeny) //許可權不足的時候的邏輯處理

。authenticationEntryPoint(authenticationEnryPoint) //未登入是的邏輯處理

。and()

。sessionManagement()

。maximumSessions(1) //最多隻能一個使用者登入一個賬號

。expiredSessionStrategy(sessionInformationExpiredStrategy) //異地登入的邏輯處理

}

}

application。yml配置檔案

server:

port: 80

spring:

datasource:

url: jdbc:mysql://localhost:3306/springsecurity_test?characterEncoding=utf8&serverTimezone=UTC

username: root

password: 123456

driver-class-name: com。mysql。cj。jdbc。Driver