JWT- SpringBoot(19)

来源:https://www.cnblogs.com/liwenruo/archive/2022/08/10/16554793.html
-Advertisement-
Play Games

在生產環境中,對發在的API增加授權保護是非常必要的。JWT作為一個無狀態的授權校撿技術,非常適合於分散式系統架構。伺服器端不需要保存用戶狀態,因此,無須採用Redis等技術來實現各個服務節點之間共用Session數據。 本節通過實例講解如何用JWT技術進行授權認證和保護。 1.1 配置安全類 (1 ...


  在生產環境中,對發在的API增加授權保護是非常必要的。JWT作為一個無狀態的授權校撿技術,非常適合於分散式系統架構。伺服器端不需要保存用戶狀態,因此,無須採用Redis等技術來實現各個服務節點之間共用Session數據。

  本節通過實例講解如何用JWT技術進行授權認證和保護。

  1.1 配置安全類

  (1)自定義用戶

查看代碼
 package com.intehel.jwt.domain;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Entity
@Data
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean accountNonExpired;
    private Boolean accountNonLocked;
    private Boolean credentialsNonExpired;
    @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
    private List<Role> roles;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }
    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }
    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

  (2)自定義角色

查看代碼
 package com.intehel.jwt.domain;

import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private String nameZh;
}

  (3)JPA

package com.intehel.jwt.repository;

import com.intehel.jwt.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Integer> {
    User findUserByUsername(String username);
}
package com.intehel.jwt.repository;
import com.intehel.jwt.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface UserRoleRepository extends JpaRepository<Role,Integer> {
    Role findByName(String name);
}

  (4)認證失敗和認證成功處理器

查看代碼
package com.intehel.jwt.handler;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Author:李自航
 * @Description:
 * @CreateDate:2022/8/5 10:15
 * @UPdateDate:2022/8/5 10:15
 * @Version:版本號
 */

@Component
public class JwtAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        request.setCharacterEncoding("UTF-8");
        String name = request.getParameter("name");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("{\n" +
                "\t\"status\":\"error\",\n" +
                "\t\"message\":\"用戶名或密碼錯誤\"\n" +
                "}");
        out.flush();
        out.close();
    }
}
查看代碼
 package com.intehel.jwt.handler;


import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
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;

@Component
public class JwtAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal != null && principal instanceof UserDetails){
            UserDetails user = (UserDetails) principal;
            request.getSession().setAttribute("userDetail",user);
            String role = "";
            Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
            for (GrantedAuthority authority : authorities){
                role = authority.getAuthority();
            }
            String token = "灌水灌水";
            response.setHeader("token",token);
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write("{\n" +
                    "\t\"status\":\"ok\",\n" +
                    "\t\"message\":\"登錄成功\"\n" +
                    "}\n");
            out.flush();
            out.close();
        }

    }
}

  (5)配置安全類

package com.intehel.jwt.config;

import com.intehel.jwt.handler.JwtAuthenticationFailHandler;
import com.intehel.jwt.handler.JwtAuthenticationSuccessHandler;
import com.intehel.jwt.handler.MyAuthenticationFailureHandler;
import com.intehel.jwt.handler.MyAuthenticationSuccessHandler;
import com.intehel.jwt.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private JwtAuthenticationFailHandler myAuthenticationFailureHandler;
    @Autowired
    MyUserDetailsService jwtDetailsService;
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/jwt/**")
                .formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/doLogin")
                .loginPage("/mylogin.html")
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenticationFailureHandler)
                .and()
                .authorizeRequests()
                .antMatchers("/register/mobile").permitAll()
                .antMatchers("/article/**").authenticated()
                .antMatchers("/jwt/tasks/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
        http.logout().permitAll();
        http.cors().and().csrf().ignoringAntMatchers("/jwt/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(jwtDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/jwt/register/mobile");

    }
}

  從上面代碼可以看出,此處JWT的安全配置和上面已經講解過的安全配置並無區別,沒有特別的參數需要配置。

  1.2 自定義登錄界面

查看代碼
 <!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登錄</title>
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<style>
    #login .container #login-row #login-column #login-box {
        border: 1px solid #9c9c9c;
        background-color: #EAEAEA;
    }
</style>
<body>
<div id="login">
    <div class="container">
        <div id="login-row" class="row justify-content-center align-items-center">
            <div id="login-column" class="col-md-6">
                <div id="login-box" class="col-md-12">
                    <form id="login-form" class="form" action="/doLogin" method="post">
                        <h3 class="text-center text-info">登錄</h3>
                        <!--/*@thymesVar id="SPRING_SECURITY_LAST_EXCEPTION" type="com"*/-->
                        <div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div>
                        <div class="form-group">
                            <label for="username" class="text-info">用戶名:</label><br>
                            <input type="text" name="username" id="username" class="form-control">
                        </div>
                        <div class="form-group">
                            <label for="password" class="text-info">密碼:</label><br>
                            <input type="text" name="password" id="password" class="form-control">
                        </div>
                        <div class="form-group">
                            <input type="submit" name="submit" class="btn btn-info btn-md" value="登錄">
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

  1.3 處理註冊

  在註冊時為了安全,需要將註冊的密碼經過加密再寫入資料庫中。

   spring security 5之後,需要對密碼添加這個類型(id),可參考文章www.cnblogs.com/majianming/p/7923604.html

  

查看代碼
 package com.intehel.jwt.controller;

import com.intehel.jwt.domain.Role;
import com.intehel.jwt.domain.User;
import com.intehel.jwt.repository.UserRepository;
import com.intehel.jwt.repository.UserRoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/jwt")
public class JwtUserController {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserRoleRepository userRoleRepository;
    @RequestMapping(value = "/register/mobile")
    public String register(User user){
        try {
            User userName = userRepository.findUserByUsername(user.getUsername());
            if (userName != null){
                return "用戶名已存在";
            }
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            user.setPassword("{bcrypt}"+encoder.encode(user.getPassword()));
            List<Role> roles = new ArrayList<>();
            Role role = userRoleRepository.findByName("ROLE_admin");
            roles.add(role);
            user.setRoles(roles);
            userRepository.save(user);
        }catch (Exception e){
            return "出現了異常";
        }
        return "成果";
    }
}

  1.4 處理登錄

查看代碼
 package com.intehel.jwt.service;

import com.intehel.jwt.domain.User;
import com.intehel.jwt.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {
        User user = userRepository.findUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用戶不存在");
        }
        return user;
    }
}

  測試多方式註冊和登錄

  1.測試註冊功能

  這裡使用測試工具Postman提交POST註冊請求

  

  資料庫插入信息如下

  

  2. 測試登錄功能

  瀏覽器輸入http://localhost:8080/jwt自動跳轉至登錄界面,輸入使用postman註冊的賬號即可

  以本博客對spring security的隨筆,可實現使用token授權登錄,這裡不多做解釋


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MySQL之JDBC 一、JDBC是什麼 Java DatabaseConnectivity (java語言連接資料庫) 二、JDBC的本質 JDBC是SUN公司制定的一套介面(interface)。 介面都有調用者和實現者。 面向介面調用、面向介面寫實現類,這都屬於面向介面編程。 三、為什麼要面向 ...
  • 1.認識shiro 除Spring Security安全框架外,應用非常廣泛的就是Apache的強大又靈活的開源安全框架 Shiro,在國內使用量遠遠超過Spring Security。它能夠用於身份驗證、授權、加密和會話管理, 有易於理解的API,可以快速、輕鬆地構建任何應用程式。而且大部分人覺得 ...
  • 面向對象編程(高級) 筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 類變數和類方法(static) 類變數 類變數-提出問題 提出問題的主要目的就是讓大家思考解決之道,從而引出我要講的知識點.說:有一群小孩在玩堆雪人,不時有新的小 ...
  • Java常用類(一) 一、String 類:(不可變的字元序列) 1.1 String:字元串,使用一對 " " 引起來表示。 String 類聲明為 final 的,不可被繼承。 String 類實現了 Serializable 介面:表示字元串是支持序列化的。實現了 Comparable 介面: ...
  • 哈嘍兄弟們,又是新的一天!今天你敲代碼了嗎? 一、序言 為什麼要挑戰自己在代碼里不寫 for loop?因為這樣可以迫使你去學習使用比較高級、比較地道的語法或 library。文中以 python 為例子,講了不少大家其實在別人的代碼里都見過、但自己很少用的語法。 自從我開始探索 Python 中驚 ...
  • (防扒小助手) 本人CSDN博客: https://blog.csdn.net/m0_61753302 本人博客園博客(同步CSDN): 何以牽塵 - 博客園 (cnblogs.com)https://www.cnblogs.com/kalesky/ 如果對你有用的話歡迎點贊關註喲! ​​​​​​​ ...
  • 一、前言 ReentrantLock 是基於 AQS 實現的同步框架,關於 AQS 的源碼在 這篇文章 已經講解過,ReentrantLock 的主要實現都依賴AQS,因此在閱讀本文前應該先瞭解 AQS 機制。本文並不關註 ReentrantLock 如何使用,只敘述其具體實現。 二、Reentra ...
  • Java集合01 1.什麼是集合? 前面我們保存數據使用的是數組,數組有不足的地方,我們來分析一下: 長度開始時必須指定,而且一但指定不能更改 保存的必須是同一類型的元素 使用數組進行增加/刪除元素的很麻煩 重新創建一個數組,將舊數組的元素拷貝過來 集合的好處: 可以動態地保存任意多個對象,使用比較 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...