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

2020-12-11 葉落花無眠

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字符串

輸入帳號錯誤,返回用戶名錯誤的json字符串 , 需說明一點,/login是springsecurity封裝好的接口,無須你在controller寫login接口,/logout也同理。

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

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

登錄成功並且擁有權限訪問指定資源, 返回資源相關數據的json字符串

登錄成功但無權限訪問指定資源時,返回權限不足的json字符串

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

註銷成功,返回註銷成功的json字符串並刪除cookie

2、核心部分

2.1、springsecurity原理解釋:

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

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

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

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

複雜理解:

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

UsernamePasswordAuthenticationFilter

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

UsernamePasswordAuthenticationToken

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

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

AuthenticationProvider

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

(2)下面跳轉到了

SelfAuthenticationProvider

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

Authentication authenticate(Authentication authentication)

自定義認證邏輯, 然後在該類中通過調用

UserDetails loadUserByUsername(account)

去獲取資料庫用戶信息並驗證,然後創建

並將權限、用戶個人信息注入到其中 ,並通過

setAuthenticated(true)

設置為需要驗證。

(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依賴文件

<dependencies> <!--轉換成json字符串的工具--> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.2</version> </dependency> <!--springboot集成web操作7--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--springsecurity--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--mysql驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--lombok依賴--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--mybatis-plus依賴--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <!--springboot-自帶測試工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.3.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.3.4.RELEASE</version> </dependency> </dependencies>

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

@Data@NoArgsConstructor@AllArgsConstructorpublic class Msg {int code; //錯誤碼 String Message; //消息提示 Map<String,Object> data=new HashMap<String,Object>(); //數據 //無權訪問 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實體類

@Datapublic class User implements Serializable {private Integer id; private String account; private String password; private String role;}

UserMapper.java ,此接口繼承 BaseMapper<T>類,而BaseMapper<T>類 封裝了大量的sql,極大程度簡化了程式設計師sql語句的書寫.

@Repositorypublic interface UserMapper extends BaseMapper<User> {}

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

public interface UserService{}

UserServiceImpl.java,使其實現 UserDetailsService接口, 從而去獲取資料庫用戶信息,詳細解析請看注釋部分。

@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService,UserDetailsService {@Autowired UserMapper userMapper; //加載用戶 @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //mybatis-plus幫我們寫好了sql語句,相當於 select * from user where account ='${account}' QueryWrapper<User> wrapper=new QueryWrapper<>(); wrapper.eq("account",s); User user=userMapper.selectOne(wrapper); //user即為查詢結果 if(user==null){ throw new UsernameNotFoundException("用戶名錯誤!!"); } //獲取用戶權限,並把其添加到GrantedAuthority中 List<GrantedAuthority> 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

@RestControllerpublic class UserController {@GetMapping("index") public String index(){ return "index"; } @GetMapping("hello") public String hello(){ return "hello"; }}

AuthenticationEnryPoint .java 自定義未登錄的處理邏輯

@Componentpublic 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 自定義登錄失敗時的處理邏輯

//登錄失敗返回給前端消息@Componentpublic 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 自定義登錄成功時的處理邏輯

@Componentpublic 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 自定義註銷時的處理邏輯

@Componentpublic 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 自定義無權限訪問時的邏輯處理

//無權訪問@Componentpublic 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 自定義異地登錄、帳號下線時的邏輯處理

@Componentpublic 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 自定義認證邏輯處理

@Componentpublic 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: 80spring: datasource: url: jdbc:mysql://localhost:3306/springsecurity_test?characterEncoding=utf8&serverTimezone=UTC username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver

相關焦點

  • 超全的springboot+springsecurity實現前後端分離簡單實現!
    通過各種方式學習springsecurity,在B站、騰訊課堂、網易課堂、慕課網沒有springsecurity的前後端分離的教學視頻,那我就去csdn去尋找springsecurity博客,發現幾個問題:  實在不行我又跑去github上找開源項目學習,github由於是外國網站,國內訪問速度有點慢!!
  • spring security簡單教程以及實現完全前後端分離
    security是spring家族的一個安全框架,入門簡單。在新手入門使用時,只需要簡單的配置,即可實現登錄以及權限的管理,無需自己寫功能邏輯代碼。但是對於現在大部分前後端分離的web程序,尤其是前端普遍使用ajax請求時,spring security自帶的登錄系統就有一些不滿足需求了。因為spring security有自己默認的登錄頁,自己默認的登錄控制器。而登錄成功或失敗,都會返回一個302跳轉。
  • spring security 整合 springboot 入門案例
    序言前面我們學習了 spring security 與 springmvc 的整合入門教程。這一節我們來學習一下 spring security 與 springboot 整合,為了力求簡單,此處不演示資料庫相關操作。
  • 「SpringSecurity-1」Springboot+Security+Oauth2授權碼模式
    本篇內容主要從搭建一個最簡單的授權伺服器開始,獲取到授權碼。話不多說開始搞……項目搭建項目中使用到的相關框架的版本號:springboot版本:2.3.0.RELEASEspring-security-oauth2版本:2.3.3.RELEASE創建認證伺服器添加依賴pom.xml中添加依賴,特別說明這是一個父子項目,父pom中添加了spring-boot-dependencies
  • Springboot + Vue + shiro 實現前後端分離、權限控制
    原先項目採用Springboot+freemarker模版,開發過程中覺得前端邏輯寫的實在噁心,後端Controller層還必須返回Freemarker模版的ModelAndView,逐漸有了前後端分離的想法,由於之前,沒有接觸過,主要參考的還是網上的一些博客教程等,初步完成了前後端分離,在此記錄以備查閱。
  • SpringSecurity + JWT前後端分離架構實現
    如何實現無狀態登錄的流程:首先客戶端發送帳戶名/密碼到服務端進行認證認證通過後,服務端將用戶信息加密並且編碼成一個token,返回給客戶端以後客戶端每次發送請求,都需要攜帶認證的token服務端對客戶端發送來的token進行解密,判斷是否有效,並且獲取用戶登錄信息在前後端分離的項目中
  • 「SpringSecurity-3」Springboot+Security + Oauth2授權碼模式
    上一篇我們實現了授權碼的整個流程,並且添加了資源伺服器,通過獲取到的token去訪問資源伺服器提供的REST接口,現在對第一篇中提到的幾個小任務進行調研修改下。(4)獲取授權碼的用戶登錄頁面是否可以定製,如何操作?授權登錄頁面自定義實現授權登陸頁修改第一步修改security配置文件。
  • 船新「漲薪秘籍」阿里巴巴內部爆款頂配版Spring Security筆記
    由於它是Spring生態系統中的一員,因此它伴隨著整個Spring生態系統不斷修正、升級,在spring boot項目中加入springsecurity更是十分簡單,使用Spring Security 減少了為企業系統安全控制編寫大量重複代碼的工作。
  • Spring boot + Spring Security實現權限管理
    2.使用Spring Security的FormLogin模式實現登錄認證3.基於JSON的前後端分離開發的登錄認證4.將權限管理系統部署到阿里雲的docker;5.基於MySQL資料庫的認證和授權。基於JSON的前後端分離開發的登錄認證前面的例子,在發送登錄請求並認證成功之後,頁面會跳轉回原訪問頁,但在前後端分離開發、通過JSON數據完成交互的應用中,會在登錄時返回一段JSON數據,告知前端登錄成功與否,由前端決定如何處理後續邏輯,而非由伺服器主動執行頁面跳轉,下面我們就看看這種情況如何實現。
  • 一款小清新的 SpringBoot+ Mybatis 前後端分離後臺管理系統項目
    一二線城市知名 IT 網際網路公司名單(新版)項目介紹前後端分離架構,分離開發,分離部署,前後端互不影響。前端技術採用vue + antdvPro + axios。後端採用spring boot + mybatis-plus + hutool等,開源可靠。
  • SpringSecurity代碼實現JWT接口權限授予與校驗
    那麼本節就用代碼來具體的實現一下JWT登錄認證及鑑權的流程。為了大部分的移動端用戶觀看,本文所有代碼均用圖片的形式發布,圖片點擊可放大。一、環境準備工作建立Spring Boot項目併集成了Spring Security,項目可以正常啟動通過controller寫一個HTTP的GET方法服務接口,比如:「/hello」實現最基本的動態數據驗證及權限分配,即實現UserDetailsService接口和UserDetails接口。
  • springboot+jpa+thymeleaf實現信息增刪改查功能
    前端:thymeleaf後端:springboot+jpa資料庫:mysql5.6jdk:1.8及以上;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController
  • SpringSecurity + JWT,從入門到精通!
    在前後端分離的項目中,你知道怎麼Spring security整合JWT麼,來看看這篇文章哈!>創建一個 JwtUser 實現 UserDetails創建 一個相關的 JavaBeanpackage com.example.demo;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails
  • SpringBoot實現通過接口動態的添加或刪除數據源
    實現的大概邏輯AbstractRoutingDataSource 類自己寫代碼維護幾個數據源並不是難事兒,主要是自己維護的數據源需要用上spring的聲明式事務,這就需要使用Spring提供的工具類:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
  • Spring Boot Security 詳解
    </groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId>
  • 基於springboot的mysql實現讀寫分離
    在實際的使用中,凡是涉及到寫的部分直接切換到主庫,讀的部分直接切換到讀庫,這就是典型的讀寫分離技術。本篇博文將聚焦讀寫分離,探討如何實現它。具體的解決方法最簡單的就是將讀請求暫時指向主庫,但是同時也失去了主從分離的部分意義。也就是說在嚴格意義上的數據一致性場景中,讀寫分離並非是完全適合的,注意更新的時效性是讀寫分離使用的缺點。
  • SpringBoot 接口冪等性的實現方案
    七、實現接口冪等示例這裡使用防重 Token 令牌方案,該方案能保證在不同請求動作下的冪等性,實現邏輯可以看上面寫的」防重 Token 令牌」方案,接下來寫下實現這個邏輯的代碼。</groupId>    <artifactId>springboot-idempotent-token</artifactId>    <version>0.0.1</version>    <name>springboot-idempotent-token</name>    &
  • springboot整合mybatis實現配置多數據源
    前言:實際開發中,隨著業務的擴張,使用單一的資料庫顯然有點臃腫,不便管理,經常會實現將不同的業務模塊的數據表結構放在各自的資料庫中,下邊簡單實現sprongboot實現多數據源的配置。一 項目結構:二 涉及到的資料庫:三 springboot中的application.properties文件:本次demo簡單配置下兩個數據源為例。
  • SpringBoot+MySQL實現讀寫分離就這麼簡單,幾步搞定!
    常用的有以下的實現方法:讀寫分離、加緩存、主從架構集群、分庫分表等,在網際網路應用中,大部分都是讀多寫少的場景,設置兩個庫,主庫和讀庫。在實際的使用中,凡是涉及到寫的部分直接切換到主庫,讀的部分直接切換到讀庫,這就是典型的讀寫分離技術。本篇博文將聚焦讀寫分離,探討如何實現它。
  • SpringBoot(五) :spring data jpa 的使用
    08/20/springboot(五)-spring-data-jpa的使用.html如有好文章投稿,請點擊 → 這裡了解詳情在上篇文章《 springboot(二):web綜合開發 》中簡單介紹了一下spring data jpa的基礎性使用,這篇文章將更加全面的介紹spring data jpa 常見用法以及注意事項。