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

2021-02-13 java1234

點擊上方藍色字體,選擇「標星公眾號」

優質文章,第一時間送達

66套java從入門到精通實戰課程分享

1、前言部分1.1、嘮嗑部分(如何學習?)

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

  實在不行我又跑去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) 去獲取資料庫用戶信息並驗證,然後創建 UsernamePasswordAuthenticationToken 並將權限、用戶個人信息注入到其中 ,並通過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
@AllArgsConstructor
public 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實體類

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

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

@Repository
public interface UserMapper extends BaseMapper<User> {
 
}

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

public interface UserService{
 
}

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

@Service
public 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

@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

最終結果在上面就有,源碼地址:  https://gitee.com/liu-wenxin/springsecurity_demo.git  , 使用 git clone https://gitee.com/liu-wenxin/springsecurity_demo.git  下載到本地。

版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本聲明。

本文連結:

https://blog.csdn.net/weixin_42375707/article/details/110678638

粉絲福利:Java從入門到入土學習路線圖

👇👇👇

感謝點讚支持下哈 

相關焦點

  • Spring Security 簡單教程以及實現完全前後端分離
    在新手入門使用時,只需要簡單的配置,即可實現登錄以及權限的管理,無需自己寫功能邏輯代碼。但是對於現在大部分前後端分離的web程序,尤其是前端普遍使用ajax請求時,spring security自帶的登錄系統就有一些不滿足需求了。因為spring security有自己默認的登錄頁,自己默認的登錄控制器。而登錄成功或失敗,都會返回一個302跳轉。
  • SpringSecurity+JWT實現前後端分離的使用
    SpringSecurity+JWT實現前後端分離的使用
  • SpringBoot整合SpringSecurity實現JWT認證
    /jpgzhu/article/details/105200598前言微服務架構,前後端分離目前已成為網際網路項目開發的業界標準,其核心思想就是前端(APP、小程序、H5 頁面等)通過調用後端的 API 接口,提交及返回 JSON 數據進行交互。
  • Spring Security 真正的前後分離實現
    Spring Security網絡上很多前後端分離的示例很多都不是完全的前後分離,而且大家實現的方式各不相同,有的是靠自己寫攔截器去自己校驗權限的,有的頁面是使用themleaf來實現的不是真正的前後分離,看的越多對Spring Security越來越疑惑,此篇文章要用最簡單的示例實現出真正的前後端完全分離的權限校驗實現。
  • SpringBoot整合SpringSecurity實現動態權限控制
    --springboot整合SpringSecurity-->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-security<
  • Springboot + Vue + shiro 實現前後端分離、權限控制
    原先項目採用Springboot+freemarker模版,開發過程中覺得前端邏輯寫的實在噁心,後端Controller層還必須返回Freemarker模版的ModelAndView,逐漸有了前後端分離的想法,由於之前,沒有接觸過,主要參考的還是網上的一些博客教程等,初步完成了前後端分離,在此記錄以備查閱。
  • Springboot+layui前後端分離實現word轉pdf功能
    =100MBspring.servlet.multipart.max-file-size=20MBspring.servlet.multipart.file-size-threshold=20MBDocExclPDFUtils.javapackage com.example.demo.utils;import java.io.File
  • SpringBoot集成SpringSecurity
    認識SpringSecuritySpring Security 是針對Spring項目的安全框架,也是Spring Boot底層安全模塊默認的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入 spring-boot-starter-security 模塊,進行少量的配置,即可實現強大的安全管理!
  • Spring Security+JWT+Vue實現一個前後端分離無狀態認證Demo
    ,首先需要實現一個簡單的 User 對象實現 UserDetails 接口,UserDetails 接口負責提供核心用戶的信息,如果你只需要用戶登陸的帳號密碼,不需要其它信息,如驗證碼等,那麼你可以直接使用 Spring Security 默認提供的 User 類,而不需要自己實現。
  • SpringBoot整合SpringSecurity示例實現前後分離權限註解+JWT登錄認證
    JWT是在Web應用中安全傳遞信息的規範,從本質上來說是Token的演變,是一種生成加密用戶身份信息的Token,特別適用於分布式單點登陸的場景,無需在服務端保存用戶的認證信息,而是直接對Token進行校驗獲取用戶信息,使單點登錄更為簡單靈活。項目環境
  • Springboot+Vue實現上傳文件顯示進度條效果功能
    1、springboot+mybatis+vue前後端分離實現用戶登陸註冊功能2、SpringBoot+Vue前後分離實現郵件發送功能3、SpringBoot+Spring Data JPA+Vue前後端分離實現分頁功能4、SpringBoot+Spring Data JPA+Vue前後端分離實現
  • SpringBoot中使用Spring Security
    </groupId>            <artifactId>spring-boot-starter-security</artifactId>        </dependency>       <!
  • Re:從零開始的Spring Security OAuth2(一)
    需要對spring security有一定的配置使用經驗,用戶認證這一塊,spring security oauth2建立在spring security的基礎之上。第一篇文章主要是講解使用springboot搭建一個簡易的授權,資源伺服器,在文末會給出具體代碼的github地址。後續文章會進行spring security oauth2的相關源碼分析。
  • springboot +security+oauth2.0 簡單教程
    .html,裡面有詳細的oauth2介紹,包括原理、實現流程等都講得比較詳細。準備 新建一個springboot項目,引入以下依賴。,這裡的資源伺服器與授權伺服器以內部類的形式實現。;import org.springframework.security.core.AuthenticationException;import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;import org.springframework.security.web.AuthenticationEntryPoint
  • Springboot+Vue實現滑動驗證成功後登錄功能
    1、springboot+mybatis+vue前後端分離實現用戶登陸註冊功能2、SpringBoot+Vue前後分離實現郵件發送功能3、SpringBoot+Spring Data JPA+Vue前後端分離實現分頁功能4、SpringBoot+Spring Data JPA+Vue前後端分離實現
  • Springboot+Vue實現批量文件上傳(pdf、word、excel)並支持在線預覽功能
    1、springboot+mybatis+vue前後端分離實現用戶登陸註冊功能2、SpringBoot+Vue前後分離實現郵件發送功能3、SpringBoot+Spring Data JPA+Vue前後端分離實現分頁功能4、SpringBoot+Spring Data JPA+Vue前後端分離實現
  • SpringBoot框架開發的優秀的項目「值得收藏學習」
    銘飛系統不僅一套簡單好用的開源系統、更是一整套優質的開源生態內容體系。/projects/spring-bootSpringSecurity認證和授權框架https://spring.io/projects/spring-securityMyBatisORM框架http://www.mybatis.org/mybatis-3/zh/index.htmlMyBatisGenerator
  • SpringBoot Security 集成JWT實現接口可信賴認證
    =levispring.security.user.password=123456MVC Security默認的安全配置在SecurityAutoConfiguration和UserDetailsServiceAutoConfiguration中實現。
  • Spring Boot 整合Spring Security示例實現前後分離權限註解+JWT登錄認證
    JWT是在Web應用中安全傳遞信息的規範,從本質上來說是Token的演變,是一種生成加密用戶身份信息的Token,特別適用於分布式單點登陸的場景,無需在服務端保存用戶的認證信息,而是直接對Token進行校驗獲取用戶信息,使單點登錄更為簡單靈活。
  • SpringBoot框架開發的優秀的項目「值得收藏學習」 - 第335
    銘飛系統不僅一套簡單好用的開源系統、更是一整套優質的開源生態內容體系。/projects/spring-bootSpringSecurity認證和授權框架https://spring.io/projects/spring-securityMyBatisORM框架http://www.mybatis.org/mybatis-3/zh/index.htmlMyBatisGenerator