1. 程式人生 > >Spring Boot 2.X(十八):整合 Spring Security-登入認證和許可權控制

Spring Boot 2.X(十八):整合 Spring Security-登入認證和許可權控制

前言

在企業專案開發中,對系統的安全和許可權控制往往是必需的,常見的安全框架有 Spring Security、Apache Shiro 等。本文主要簡單介紹一下 Spring Security,再通過 Spring Boot 整合開一個簡單的示例。

Spring Security

什麼是 Spring Security?

Spring Security 是一種基於 Spring AOP 和 Servlet 過濾器 Filter 的安全框架,它提供了全面的安全解決方案,提供在 Web 請求和方法呼叫級別的使用者鑑權和許可權控制。

Web 應用的安全性通常包括兩方面:使用者認證(Authentication)和使用者授權(Authorization)。

使用者認證指的是驗證某個使用者是否為系統合法使用者,也就是說使用者能否訪問該系統。使用者認證一般要求使用者提供使用者名稱和密碼,系統通過校驗使用者名稱和密碼來完成認證。

使用者授權指的是驗證某個使用者是否有許可權執行某個操作。

2.原理

Spring Security 功能的實現主要是靠一系列的過濾器鏈相互配合來完成的。以下是專案啟動時列印的預設安全過濾器鏈(整合5.2.0):

[
    org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@5054e546,
    org.springframework.security.web.context.SecurityContextPersistenceFilter@7b0c69a6,
    org.springframework.security.web.header.HeaderWriterFilter@4fefa770,
    org.springframework.security.web.csrf.CsrfFilter@6346aba8,
    org.springframework.security.web.authentication.logout.LogoutFilter@677ac054,
    org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@51430781,
    org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4203d678,
    org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@625e20e6,
    org.springframework.security.web.authentication.AnonymousAuthenticationFilter@19628fc2,
    org.springframework.security.web.session.SessionManagementFilter@471f8a70,
    org.springframework.security.web.access.ExceptionTranslationFilter@3e1eb569,
    org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3089ab62
]
  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CsrfFilter
  • LogoutFilter
  • UsernamePasswordAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • AnonymousAuthenticationFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor

詳細解讀可以參考:https://blog.csdn.net/dushiwodecuo/article/details/78913113

3.核心元件

SecurityContextHolder

用於儲存應用程式安全上下文(Spring Context)的詳細資訊,如當前操作的使用者物件資訊、認證狀態、角色許可權資訊等。預設情況下,SecurityContextHolder 會使用 ThreadLocal 來儲存這些資訊,意味著安全上下文始終可用於同一執行執行緒中的方法。

獲取有關當前使用者的資訊

因為身份資訊與執行緒是繫結的,所以可以在程式的任何地方使用靜態方法獲取使用者資訊。例如獲取當前經過身份驗證的使用者的名稱,程式碼如下:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
    String username = ((UserDetails)principal).getUsername();
} else {
    String username = principal.toString();
}

其中,getAuthentication() 返回認證資訊,getPrincipal() 返回身份資訊,UserDetails 是對使用者資訊的封裝類。

Authentication

認證資訊介面,集成了 Principal 類。該介面中方法如下:

介面方法 功能說明
getAuthorities() 獲取許可權資訊列表,預設是 GrantedAuthority 介面的一些實現類,通常是代表權限資訊的一系列字串
getCredentials() 獲取使用者提交的密碼憑證,使用者輸入的密碼字元竄,在認證過後通常會被移除,用於保障安全
getDetails() 獲取使用者詳細資訊,用於記錄 ip、sessionid、證書序列號等值
getPrincipal() 獲取使用者身份資訊,大部分情況下返回的是 UserDetails 介面的實現類,是框架中最常用的介面之一

AuthenticationManager

認證管理器,負責驗證。認證成功後,AuthenticationManager 返回一個填充了使用者認證資訊(包括許可權資訊、身份資訊、詳細資訊等,但密碼通常會被移除)的 Authentication 例項。然後再將 Authentication 設定到 SecurityContextHolder 容器中。

AuthenticationManager 介面是認證相關的核心介面,也是發起認證的入口。但它一般不直接認證,其常用實現類 ProviderManager 內部會維護一個 List<AuthenticationProvider> 列表,存放裡多種認證方式,預設情況下,只需要通過一個 AuthenticationProvider 的認證,就可被認為是登入成功。

UserDetailsService

負責從特定的地方載入使用者資訊,通常是通過JdbcDaoImpl從資料庫載入實現,也可以通過記憶體對映InMemoryDaoImpl實現。

UserDetails

該介面代表了最詳細的使用者資訊。該介面中方法如下:

介面方法 功能說明
getAuthorities() 獲取授予使用者的許可權
getPassword() 獲取使用者正確的密碼,這個密碼在驗證時會和 Authentication 中的 getCredentials() 做比對
getUsername() 獲取用於驗證的使用者名稱
isAccountNonExpired() 指示使用者的帳戶是否已過期,無法驗證過期的使用者
isAccountNonLocked() 指示使用者的賬號是否被鎖定,無法驗證被鎖定的使用者
isCredentialsNonExpired() 指示使用者的憑據(密碼)是否已過期,無法驗證憑證過期的使用者
isEnabled() 指示使用者是否被啟用,無法驗證被禁用的使用者

Spring Security 實戰

1.系統設計

本文主要使用 Spring Security 來實現系統頁面的許可權控制和安全認證,本示例不做詳細的資料增刪改查,sql 可以在完整程式碼裡下載,主要是基於資料庫對頁面 和 ajax 請求做許可權控制。

1.1 技術棧

  • 程式語言:Java
  • 程式設計框架:Spring、Spring MVC、Spring Boot
  • ORM 框架:MyBatis
  • 檢視模板引擎:Thymeleaf
  • 安全框架:Spring Security(5.2.0)
  • 資料庫:MySQL
  • 前端:Layui、JQuery

1.2 功能設計

  1. 實現登入、退出
  2. 實現選單 url 跳轉的許可權控制
  3. 實現按鈕 ajax 請求的許可權控制
  4. 防止跨站請求偽造(CSRF)攻擊

1.3 資料庫層設計

t_user 使用者表

欄位 型別 長度 是否為空 說明
id int 8 主鍵,自增長
username varchar 20 使用者名稱
password varchar 255 密碼

t_role 角色表

欄位 型別 長度 是否為空 說明
id int 8 主鍵,自增長
role_name varchar 20 角色名稱

t_menu 選單表

欄位 型別 長度 是否為空 說明
id int 8 主鍵,自增長
menu_name varchar 20 選單名稱
menu_url varchar 50 選單url(Controller 請求路徑)

t_user_roles 使用者許可權表

欄位 型別 長度 是否為空 說明
id int 8 主鍵,自增長
user_id int 8 使用者表id
role_id int 8 角色表id

t_role_menus 許可權選單表

欄位 型別 長度 是否為空 說明
id int 8 主鍵,自增長
role_id int 8 角色表id
menu_id int 8 選單表id

實體類這裡不詳細列了。

2.程式碼實現

2.0 相關依賴

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <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.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- 熱部署模組 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional> <!-- 這個需要為 true 熱部署才有效 -->
        </dependency>
        
            <!-- mysql 資料庫驅動. -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- mybaits -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        
        <!-- thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <!-- alibaba fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <!-- spring security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

2.1 繼承 WebSecurityConfigurerAdapter 自定義 Spring Security 配置

/**
prePostEnabled :決定Spring Security的前註解是否可用 [@PreAuthorize,@PostAuthorize,..]
secureEnabled : 決定是否Spring Security的保障註解 [@Secured] 是否可用
jsr250Enabled :決定 JSR-250 annotations 註解[@RolesAllowed..] 是否可用.
 */
@Configurable
@EnableWebSecurity
//開啟 Spring Security 方法級安全註解 @EnableGlobalMethodSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;
    @Autowired
    private UserDetailsService userDetailsService;
    
    /**
     * 靜態資源設定
     */
    @Override
    public void configure(WebSecurity webSecurity) {
        //不攔截靜態資源,所有使用者均可訪問的資源
        webSecurity.ignoring().antMatchers(
                "/",
                "/css/**",
                "/js/**",
                "/images/**",
                "/layui/**"
                );
    }
    /**
     * http請求設定
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //http.csrf().disable(); //註釋就是使用 csrf 功能       
        http.headers().frameOptions().disable();//解決 in a frame because it set 'X-Frame-Options' to 'DENY' 問題           
        //http.anonymous().disable();
        http.authorizeRequests()
            .antMatchers("/login/**","/initUserData","/main")//不攔截登入相關方法        
            .permitAll()        
            //.antMatchers("/user").hasRole("ADMIN")  // user介面只有ADMIN角色的可以訪問
//          .anyRequest()
//          .authenticated()// 任何尚未匹配的URL只需要驗證使用者即可訪問
            .anyRequest()
            .access("@rbacPermission.hasPermission(request, authentication)")//根據賬號許可權訪問         
            .and()
            .formLogin()
            .loginPage("/")
            .loginPage("/login")   //登入請求頁
            .loginProcessingUrl("/login")  //登入POST請求路徑
            .usernameParameter("username") //登入使用者名稱引數
            .passwordParameter("password") //登入密碼引數
            .defaultSuccessUrl("/main")   //預設登入成功頁面
            .and()
            .exceptionHandling()
            .accessDeniedHandler(customAccessDeniedHandler) //無許可權處理器
            .and()
            .logout()
            .logoutSuccessUrl("/login?logout");  //退出登入成功URL
            
    }
    /**
     * 自定義獲取使用者資訊介面
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    
    /**
     * 密碼加密演算法
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
 
    }
}

2.2 自定義實現 UserDetails 介面,擴充套件屬性

public class UserEntity implements UserDetails {

    /**
     * 
     */
    private static final long serialVersionUID = -9005214545793249372L;

    private Long id;// 使用者id
    private String username;// 使用者名稱
    private String password;// 密碼
    private List<Role> userRoles;// 使用者許可權集合
    private List<Menu> roleMenus;// 角色選單集合

    private Collection<? extends GrantedAuthority> authorities;
    public UserEntity() {
        
    }
    
    public UserEntity(String username, String password, Collection<? extends GrantedAuthority> authorities,
            List<Menu> roleMenus) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
        this.roleMenus = roleMenus;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<Role> getUserRoles() {
        return userRoles;
    }

    public void setUserRoles(List<Role> userRoles) {
        this.userRoles = userRoles;
    }

    public List<Menu> getRoleMenus() {
        return roleMenus;
    }

    public void setRoleMenus(List<Menu> roleMenus) {
        this.roleMenus = roleMenus;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

2.3 自定義實現 UserDetailsService 介面

/**
 * 獲取使用者相關資訊
 * @author charlie
 *
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    private Logger log = LoggerFactory.getLogger(UserDetailServiceImpl.class);

    @Autowired
    private UserDao userDao;

    @Autowired
    private RoleDao roleDao;
    @Autowired
    private MenuDao menuDao;

    @Override
    public UserEntity loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根據使用者名稱查詢使用者
        UserEntity user = userDao.getUserByUsername(username);
        System.out.println(user);
        if (user != null) {
            System.out.println("UserDetailsService");
            //根據使用者id獲取使用者角色
            List<Role> roles = roleDao.getUserRoleByUserId(user.getId());
            // 填充許可權
            Collection<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>();
            for (Role role : roles) {
                authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
            }
            //填充許可權選單
            List<Menu> menus=menuDao.getRoleMenuByRoles(roles);
            return new UserEntity(username,user.getPassword(),authorities,menus);
        } else {
            System.out.println(username +" not found");
            throw new UsernameNotFoundException(username +" not found");
        }       
    }

}

2.4 自定義實現 URL 許可權控制

/**
 * RBAC資料模型控制權限
 * @author charlie
 *
 */
@Component("rbacPermission")
public class RbacPermission{

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object principal = authentication.getPrincipal();
        boolean hasPermission = false;
        // 讀取使用者所擁有的許可權選單
        List<Menu> menus = ((UserEntity) principal).getRoleMenus();
        System.out.println(menus.size());
        for (Menu menu : menus) {
            if (antPathMatcher.match(menu.getMenuUrl(), request.getRequestURI())) {
                hasPermission = true;
                break;
            }
        }
        return hasPermission;
    }
}

2.5 實現 AccessDeniedHandler

自定義處理無權請求

/**
 * 處理無權請求
 * @author charlie
 *
 */
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private Logger log = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException, ServletException {
        boolean isAjax = ControllerTools.isAjaxRequest(request);
        System.out.println("CustomAccessDeniedHandler handle");
        if (!response.isCommitted()) {
            if (isAjax) {
                String msg = accessDeniedException.getMessage();
                log.info("accessDeniedException.message:" + msg);
                String accessDenyMsg = "{\"code\":\"403\",\"msg\":\"沒有許可權\"}";
                ControllerTools.print(response, accessDenyMsg);
            } else {
                request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
                response.setStatus(HttpStatus.FORBIDDEN.value());
                RequestDispatcher dispatcher = request.getRequestDispatcher("/403");
                dispatcher.forward(request, response);
            }
        }

    }

    public static class ControllerTools {
        public static boolean isAjaxRequest(HttpServletRequest request) {
            return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
        }

        public static void print(HttpServletResponse response, String msg) throws IOException {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(msg);
            writer.flush();
            writer.close();
        }
    }

}

2.6 相關 Controller

登入/退出跳轉

/**
 * 登入/退出跳轉
 * @author charlie
 *
 */
@Controller
public class LoginController {
    @GetMapping("/login")
    public ModelAndView login(@RequestParam(value = "error", required = false) String error,
            @RequestParam(value = "logout", required = false) String logout) {
        ModelAndView mav = new ModelAndView();
        if (error != null) {
            mav.addObject("error", "使用者名稱或者密碼不正確");
        }
        if (logout != null) {
            mav.addObject("msg", "退出成功");
        }
        mav.setViewName("login");
        return mav;
    }
}

登入成功跳轉

@Controller
public class MainController {

    @GetMapping("/main")
    public ModelAndView toMainPage() {
        //獲取登入的使用者名稱
        Object principal= SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String username=null;
        if(principal instanceof UserDetails) {
            username=((UserDetails)principal).getUsername();
        }else {
            username=principal.toString();
        }
        ModelAndView mav = new ModelAndView();
        mav.setViewName("main");
        mav.addObject("username", username);
        return mav;
    }
    
}

用於不同許可權頁面訪問測試

/**
 * 用於不同許可權頁面訪問測試
 * @author charlie
 *
 */
@Controller
public class ResourceController {

    @GetMapping("/publicResource")
    public String toPublicResource() {
        return "resource/public";
    }
    
    @GetMapping("/vipResource")
    public String toVipResource() {
        return "resource/vip";
    }
}

用於不同許可權ajax請求測試

/**
 * 用於不同許可權ajax請求測試
 * @author charlie
 *
 */
@RestController
@RequestMapping("/test")
public class HttptestController {

    @PostMapping("/public")
    public JSONObject doPublicHandler(Long id) {
        JSONObject json = new JSONObject();
        json.put("code", 200);
        json.put("msg", "請求成功" + id);
        return json;
    }

    @PostMapping("/vip")
    public JSONObject doVipHandler(Long id) {
        JSONObject json = new JSONObject();
        json.put("code", 200);
        json.put("msg", "請求成功" + id);
        return json;
    }
}

2.7 相關 html 頁面

登入頁面

<form class="layui-form" action="/login" method="post">
            <div class="layui-input-inline">
                <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
                <input type="text" name="username" required
                    placeholder="使用者名稱" autocomplete="off" class="layui-input">
            </div>
            <div class="layui-input-inline">
                <input type="password" name="password" required  placeholder="密碼" autocomplete="off"
                    class="layui-input">
            </div>
            <div class="layui-input-inline login-btn">
                <button id="btnLogin" lay-submit lay-filter="*" class="layui-btn">登入</button>
            </div>
            <div class="form-message">
                <label th:text="${error}"></label>
                <label th:text="${msg}"></label>
            </div>
        </form>

防止跨站請求偽造(CSRF)攻擊

退出系統

<form id="logoutForm" action="/logout" method="post"
                                style="display: none;">
                                <input type="hidden" th:name="${_csrf.parameterName}"
                                    th:value="${_csrf.token}">
                            </form>
                            <a
                                href="javascript:document.getElementById('logoutForm').submit();">退出系統</a>

ajax 請求頁面

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" id="hidCSRF">
<button class="layui-btn" id="btnPublic">公共許可權請求按鈕</button>
<br>
<br>
<button class="layui-btn" id="btnVip">VIP許可權請求按鈕</button>
<script type="text/javascript" th:src="@{/js/jquery-1.8.3.min.js}"></script>
<script type="text/javascript" th:src="@{/layui/layui.js}"></script>
<script type="text/javascript">
        layui.use('form', function() {
            var form = layui.form;
            $("#btnPublic").click(function(){
                $.ajax({
                    url:"/test/public",
                    type:"POST",
                    data:{id:1},
                    beforeSend:function(xhr){
                        xhr.setRequestHeader('X-CSRF-TOKEN',$("#hidCSRF").val());   
                    },
                    success:function(res){
                        alert(res.code+":"+res.msg);
                
                    }   
                });
            });
            $("#btnVip").click(function(){
                $.ajax({
                    url:"/test/vip",
                    type:"POST",
                    data:{id:2},
                    beforeSend:function(xhr){
                        xhr.setRequestHeader('X-CSRF-TOKEN',$("#hidCSRF").val());   
                    },
                    success:function(res){
                        alert(res.code+":"+res.msg);
                        
                    }
                });
            });
        });
    </script>

2.8 測試

測試提供兩個賬號:user 和 admin (密碼與賬號一樣)

由於 admin 作為管理員許可權,設定了全部的訪問許可權,這裡只展示 user 的測試結果。



完整程式碼

github

碼雲

非特殊說明,本文版權歸 朝霧輕寒 所有,轉載請註明出處.

原文標題:Spring Boot 2.X(十八):整合 Spring Security-登入認證和許可權控制

原文地址:https://www.zwqh.top/article/info/27

如果文章有不足的地方,歡迎提點,後續會完善。

如果文章對您有幫助,請給我點個贊,請掃碼關注下我的公眾號,文章持續更新中...

相關推薦

Spring Boot 2.X()整合 Spring Security-登入認證許可權控制

前言 在企業專案開發中,對系統的安全和許可權控制往往是必需的,常見的安全框架有 Spring Security、Apache Shiro 等。本文主要簡單介紹一下 Spring Security,再通過 Spring Boot 整合開一個簡單的示例。 Spring Security 什麼是 Spring Se

Spring Boot 2.X(五)整合 Swagger2 開發 API 文件(線上+離線)

前言 相信很多後端開發在專案中都會碰到要寫 api 文件,不管是給前端、移動端等提供更好的對接,還是以後為了以後交接方便,都會要求寫 api 文件。 而手寫 api 文件的話有諸多痛點: 文件更新的時候,需要再次傳送給對接人 介面太對,手寫文件很難管理 介面返回的結果不明確 不能直接線上測試介面,通常需要使

Spring Boot 2.x 基礎案例整合Dubbo 2.7.3+Nacos1.1.3(最新版)

1、概述 本文將介紹如何基於Spring Boot 2.x的版本,通過Nacos作為配置與註冊中心,實現Dubbo服務的註冊與消費。 整合元件的版本說明: Spring Boot 2.1.9 Dubbo 2.7.3 Nacos 1.1.3 本文的亮點: 1.採用yml方式進行dubbo的配置。 2.

Spring Boot 2.x 基礎案例整合Dubbo 2.7.3+Nacos1.1.3(配置中心)

本文原創首發於公眾號:Java技術乾貨 1、概述 本文將Nacos作為配置中心,實現配置外部化,動態更新。這樣做的優點:不需要重啟應用,便可以動態更新應用裡的配置資訊。在如今流行的微服務應用下,將應用的配置統一管理,顯得尤為重要。 上一篇寫了《Spring Boot 2.x 基礎案例:整合Dubbo

Spring Boot 2.X(一)全域性異常處理

前言 在 Java Web 系統開發中,不管是 Controller 層、Service 層還是 Dao 層,都有可能丟擲異常。如果在每個方法中加上各種 try catch 的異常處理程式碼,那樣會使程式碼非常繁瑣。在Spring MVC 中,我們可以將所有型別的異常處理從各個單獨的方法中解耦出來,進行異常資

Spring Boot 2.X(二)定時任務

簡介 定時任務是後端開發中常見的需求,主要應用場景有定期資料報表、定時訊息通知、非同步的後臺業務邏輯處理、日誌分析處理、垃圾資料清理、定時更新快取等等。 Spring Boot 集成了一整套的定時任務工具,讓我們專注於完成邏輯,剩下的基礎排程工作將自動完成。 通用實現方式 實現方式 描述 jav

Spring Boot 2.X(四)日誌功能 Logback

Logback 簡介 Logback 是由 SLF4J 作者開發的新一代日誌框架,用於替代 log4j。 主要特點是效率更高,架構設計夠通用,適用於不同的環境。 Logback 分為三個模組:logback-core,logback-classic和logback-access。 logback-core 模

Spring Boot 2.X(六)應用監控之 Spring Boot Actuator 使用及配置

Actuator 簡介 Actuator 是 Spring Boot 提供的對應用系統的自省和監控功能。通過 Actuator,可以使用資料化的指標去度量應用的執行情況,比如檢視伺服器的磁碟、記憶體、CPU等資訊,系統的執行緒、gc、執行狀態等等。 Actuator 通常通過使用 HTTP 和 JMX 來管理

Spring Boot 2.x基礎教程使用Spring Data JPA訪問MySQL

在資料訪問這章的第一篇文章《Spring中使用JdbcTemplate訪問資料庫》 中,我們已經介紹瞭如何使用Spring Boot中最基本的jdbc模組來實現關係型資料庫的資料讀寫操作。那麼結合Web開發一章的內容,我們就可以利用JDBC模組與Web模組的功能,綜合著使用來完成一個適用於很多簡單應用場景的後

SpringBoot+shiro整合學習之登入認證許可權控制

學習任務目標 使用者必須要登陸之後才能訪問定義連結,否則跳轉到登入頁面。 對連結進行許可權控制,只有噹噹前登入使用者有這個連結訪問許可權才可以訪問,否則跳轉到指定頁面。 輸入錯誤密碼使用者名稱或則使用者被設定為靜止登入,返回相應json串資訊 匯

Spring Boot 2.X()自定義註冊 Servlet、Filter、Listener

前言 在 Spring Boot 中已經移除了 web.xml 檔案,如果需要註冊新增 Servlet、Filter、Listener 為 Spring Bean,在 Spring Boot 中有兩種方式: 使用 Servlet 3.0 API 的註解 @WebServlet、@WebFilter、@Lis

Spring Boot 2.x基礎教程快速入門

開發十年,就只剩下這套架構體系了! >>>   

Spring Boot 2.x基礎教程工程結構推薦

Spring Boot框架本身並沒有對工程結構有特別的要求,但是按照最佳實踐的工程結構可以幫助我們減少可能會遇見的坑,尤其是Spr

Spring Boot 2.x基礎教程構建RESTful API與單元測試

首先,回顧並詳細說明一下在快速入門中使用的@Controller、@RestController、@RequestMapping註

Spring Boot 2.x基礎教程使用Swagger2構建強大的API文件

隨著前後端分離架構和微服務架構的流行,我們使用Spring Boot來構建RESTful API專案的場景越來越多。通常我們的一個RESTful API就有可能要服務於多個不同的開發人員或開發團隊:IOS開發、Android開發、Web開發甚至其他的後端服務等。為了減少與其他團隊平時開發期間的頻繁溝通成本,傳

Spring Boot 2.x基礎教程JSR-303實現請求引數校驗

請求引數的校驗是很多新手開發非常容易犯錯,或存在較多改進點的常見場景。比較常見的問題主要表現在以下幾個方面: 僅依靠前端框架解決引數校驗,缺失服務端的校驗。這種情況常見於需要同時開發前後端的時候,雖然程式的正常使用不會有問題,但是開發者忽略了非正常操作。比如繞過前端程式,直接模擬客戶端請求,這時候就會突然在

Spring Boot 2.x基礎教程Swagger介面分類與各元素排序問題詳解

之前通過Spring Boot 2.x基礎教程:使用Swagger2構建強大的API文件一文,我們學習瞭如何使用Swagger為Spring Boot專案自動生成API文件,有不少使用者留言問了關於文件內容的組織以及排序問題。所以,就特別開一篇詳細說說Swagger中文件內容如何來組織以及其中各個元素如何控制

Spring Boot 2.x基礎教程Swagger靜態文件的生成

前言 通過之前的兩篇關於Swagger入門以及具體使用細節的介紹之後,我們已經能夠輕鬆地為Spring MVC的Web專案自動構建出API文件了。如果您還不熟悉這塊,可以先閱讀: Spring Boot 2.x基礎教程:使用Swagger2構建強大的API文件 Spring Boot 2.x基礎教程:Swa

Spring Boot 入門(整合Redis哨兵模式,實現Mybatis二級快取

本片文章續《Spring Boot 入門(九):整合Quartz定時任務》。本文主要基於redis實現了mybatis二級快取。較redis快取,mybaits自帶快取存在缺點(自行谷歌)。本文是基於docker安裝redis主從模式。 1.redis安裝 (1)首先安裝redis叢集模式,建立redis目錄

Spring Boot 2.x基礎教程使用JdbcTemplate訪問MySQL資料庫

在第2章節中,我們介紹瞭如何通過Spring Boot來實現HTTP介面,以及圍繞HTTP介面相關的單元測試、文件生成等實用技能。但是,這些內容還不足以幫助我們構建一個動態應用的服務端程式。不論我們是要做App、小程式、還是傳統的Web站點,對於使用者的資訊、相關業務的內容,通常都需要對其進行儲存,而不是像第