從事WEB開發依賴,一半用的安全認證框架,最多就是有兩種:Apache Shrio和Spring Security,而Spring Security作為Spring全家桶中的一員,相對於Spring集成起來更具有優勢和更好的支持!
![Spring Security簡介及SpringBoot整合Spring Security](http://p2.ttnews.xyz/loading.gif)
![Spring Security簡介及SpringBoot整合Spring Security](http://p2.ttnews.xyz/loading.gif)
Spring Security是一個強大且可定製的身份驗證和訪問控制框架,完全基於Spring的應用程序標準,它能夠為基於Spring的企業應用系統提供安全訪問控制解決方案的安全框架,它提供了一組可以在Spring應用上下文中配置的bean,充分利用了Spring IoC和AOP功能。用過apache shrio安全框架的碼友們都知道,安全認證框架主要包含兩個操作:認證(Authentication)和授權(Authorization)。Spring Security基於上述的兩個操作,也是提供了多個模塊:
1、核心模塊(spring-security-core.jar):包含核心的驗證和訪問控制類和接口,遠程支持和基本的配置API。任何使用Spring Security的應用程序都需要這個模塊。支持獨立應用程序、遠程客戶端、服務層方法安全和JDBC用戶配置。
2、遠程調用(spring-security-remoting.jar):提供與Spring Remoting的集成,通常我們不需要這個模塊,除非你要使用Spring Remoting編寫遠程客戶端。
3、Web網頁(spring-security-web.jar):包含網站安全相關的基礎代碼,包括Spring security網頁驗證服務和基於URL的訪問控制。
4、配置(spring-security-config.jar):包含安全命令空間的解析代碼。如果你使用Spring Security XML命令空間進行配置你需要包含這個模塊。
5、LDAP(spring-security-ldap.jar):LDAP驗證和配置代碼,這個模塊可用於LDAP驗證和管理LDAP用戶實體。
6、ACL訪問控制(spring-security-acl.jar):ACL專門的領域對象的實現。用來在你的應用程序中對特定的領域對象實例應用安全性。
7、CAS(spring-security-cas.jar):Spring Security的CAS客戶端集成。如果你想用CAS的SSO服務器使用Spring Security網頁驗證需要該模塊。
8、OpenID(spring-security-openid.jar):OpenID 網頁驗證支持。使用外部的OpenID服務器驗證用戶。
9、Test(spring-security-test.jar):支持Spring security的測試。
在SpringBoot中配置Spring security中非常簡單,在pom.xml文件中加入Spring security的依賴,由於要使用靜態模板的支持,所以把thymeleaf的依賴也給引入進來:
需要創建一個自定義類繼承WebSecurityConfigurerAdapter:
WebSecurityConfigurerAdapter是Spring security為Web應用提供的一個適配器,實現了WebSecurityConfigurer接口,提供了兩個方法用於重寫,從而實現開發者需求的安全配置。
方法configure(HttpSecurity http)可以通過http.authorizeRequests()定義哪些URL需要保護、哪些URL不需要,通過formLogin()方法定義當前用戶登陸的時候,跳轉到的登陸頁面。
方法configure(AuthenticationManagerBuilder auth)用於創建用戶和用戶的角色。
該自定義類代碼如下:
<code>package com.datang.security;import org.springframework.beans.factory.annotation.Autowired;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.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@EnableWebSecuritypublic class MySecurityConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("MySecurityConfigurer HttpSecurity 調用..."); http.authorizeRequests() .antMatchers("/login").permitAll() //都可以訪問路徑/login .antMatchers("/", "/home").hasRole("ROCKET") //角色ROCKET可訪問 .antMatchers("/admin/**").hasAnyRole("LAKER", "HEAT") //角色LAKER、HEAT可訪問 .anyRequest().authenticated() //其他URL需要認證 .and() .formLogin().loginPage("/login") //登陸請求 .successHandler(new MyAuthenticationSuccessHandler()) //登陸成功後的行為 .usernameParameter("username").passwordParameter("password") //登陸時接收的參數 .and() .logout().permitAll() //登出,都可以訪問 .and() .exceptionHandling().accessDeniedPage("/accessDenied"); //異常處理 } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { System.out.println("MySecurityConfigurer AuthenticationManagerBuilder 調用..."); auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()) .withUser("t-mac").password("rocket1").roles("ROCKET"); //創建麥迪 auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()) .withUser("james").password("laker23").roles("LAKER", "HEAT"); //創建詹姆斯 }}/<code>
上面代碼中,當登陸成功後會有一個處理,就是登陸成功後怎麼跳轉,也就是我們自己定義的handler:new MyAuthenticationSuccessHandler(),該類的代碼如下:
<code>package com.datang.security;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.web.DefaultRedirectStrategy;import org.springframework.security.web.RedirectStrategy;import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;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.util.ArrayList;import java.util.Collection;import java.util.List;@Componentpublic class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { //負責所有重定向事務 private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String targetUrl = directTargetUrl(authentication); redirectStrategy.sendRedirect(request, response, targetUrl); } protected String directTargetUrl(Authentication authentication) { String url = ""; Collection extends GrantedAuthority> authorities = authentication.getAuthorities(); List<string> roles = new ArrayList<>(); for (GrantedAuthority a: authorities) { roles.add(a.getAuthority()); } if (isAdmin(roles)) { url = "/admin"; } else if (isUser(roles)) { url = "/home"; } else { url = "/accessDenied"; } System.out.println("url = " + url); return url; } private boolean isUser(List<string> roles) { if (roles.contains("ROLE_ROCKET")) { return true; } else { return false; } } private boolean isAdmin(List<string> roles) { if (roles.contains("ROLE_LAKER")) { return true; } else { return false; } }}/<string>/<string>/<string>/<code>
該類繼承了SimpleUrlAuthenticationSuccessHandler,提供了handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)方法用於處理登陸成功後的URL重定向,其中directTargetUrl()方法是要獲取當前登陸者的角色,然後根據角色重定向到指定的URL。
在我們定義的MySecurityConfigurer類中,configure(AuthenticationManagerBuilder auth)方法也出現了一個我們自定義的類:new MyPasswordEncoder(),該類的代碼是:
<code>
它實現了PasswordEncoder接口,並實現了接口的兩個方法:encode()和matches(),這是一個密碼編輯器。
好了,現在基礎工作以及準備的差不多了,現在就寫個controller類來測試一下,首先寫一個基礎的BaseController,該類中實現了獲取用戶名getUsername()和獲取角色getAuthority()的方法,代碼如下:
<code>package com.datang.controller;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.context.SecurityContextHolder;import java.util.ArrayList;import java.util.List;public class BaseController { protected String getUsername() { String name = SecurityContextHolder.getContext().getAuthentication().getName(); return name; } protected String getAuthority() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); List<string> roles = new ArrayList<>(); for (GrantedAuthority a:authentication.getAuthorities()) { roles.add(a.getAuthority()); } return roles.toString(); }}/<string>/<code>
然後寫一個測試類MySecurityController,繼承BaseController,其中代碼如下:
<code>package com.datang.controller;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Controllerpublic class MySecurityController extends BaseController{ @RequestMapping("/") @ResponseBody public String index() { return "首頁"; } @RequestMapping("/home") @ResponseBody public String home() { String username = getUsername(); System.out.println("username = " + username); return "主頁 - 用戶名:" + username; } @RequestMapping("/login") public String login() { return "login"; } @RequestMapping("/admin") @ResponseBody public String admin() { String username = getUsername(); String role = getAuthority(); return "管理頁 - 用戶名:" + username + " - 角色:" + role; } @RequestMapping("/nba") @ResponseBody public String nba() { String username = getUsername(); String role = getAuthority(); return "NBA - 用戶名:" + username + " - 角色:" + role; } @RequestMapping("/accessDenied") @ResponseBody public String accessDenied() { return "無權限"; } @RequestMapping("/logout") @ResponseBody public String logout(HttpServletRequest request, HttpServletResponse response) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { new SecurityContextLogoutHandler().logout(request, response, authentication); } return "用戶登出"; }}/<code>
由於是簡單的測試,所以就湊合寫一個簡單的登陸頁面login.html,其他頁面就用返回字符串來代替!
login.html代碼如下:
<code> <title>登錄/<title>/<code>
然後整體代碼結構如下,紅色邊框外的類和包自行忽略呀:
啟動這個demo:
看了咱們定義的URL訪問規則和用戶創建生效了,下面可以訪問了,輸入URL:http://localhost:9092/nba或者其他的諸如http://localhost:9092/aaa,會被重定向到http://localhost:9092/login,因為沒有登陸:
輸入用戶james登陸,會被重定向到/admin,輸入t-mac會被重定向到/home:
此時再訪問http://localhost:9092/nba就可以了:
其他登出/logout也是正常登出的,自個兒動手試一下吧!
閱讀更多 程序員的苦咖啡 的文章