Spring Security使用

Spring Security

在web應用開發中,安全無疑是十分重要的,選擇Spring Security來保護web應用是一個非常好的選擇。

Spring Security 是spring項目之中的一個安全模塊,可以非常方便與spring項目無縫集成。特別是在spring boot項目中加入spring security更是十分簡單。本篇我們介紹spring security,以及spring security在web應用中的使用。

一個例子入門

假設我們現在創建好了一個springboot的web應用,有一個控制器如下:

@Controller
public class AppController {@RequestMapping("/hello")@ResponseBodyString home() {return "Hello ,spring security!";}
}

我們啟動應用,假設端口是8080,那么當我們在瀏覽器訪問http://localhost:8080/hello的時候可以在瀏覽器看到Hello ,spring security!。

加入spring security 保護應用

此時,/hello是可以自由訪問。假設,我們需要具有某個角色的用戶才能訪問的時候,我們可以引入spring security來進行保護。加入如下maven依賴,并重啟應用:

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

再次訪問/hello,我們可以得到一個http-basic的認證彈窗,如下:

?

代碼如下:

<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table><tr><td>User:</td><td><input type='text' name='username' value=''></td></tr><tr><td>Password:</td><td><input type='password' name='password'/></td></tr><tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr><input name="_csrf" type="hidden" value="635780a5-6853-4fcd-ba14-77db85dbd8bd" />
</table>
</form></body></html>

我們可以發現,這里有個form 。action=”/login”,這個/login是spring security提供的。form表單提交了三個數據:

username 用戶名? ?password 密碼? ?_csrf CSRF保護方面的內容

說明spring security 已經起作用了。這時,它為你生成了賬號和密碼,在內存中。

但是,我們不可能只是這么使用它,我們如何通過訪問數據庫來登錄,驗證權限呢?

接下來通過一個實例繼續深入。

demo項目權限介紹

我們通過一個很簡單的項目來認識一下Spring Security。

index.html:社區首頁(只有四個鏈接)----------任何人都可以訪問

discuss.html:帖子詳情頁面(只有一句話)------任何人都可以訪問

letter.html:私信列表(只有一句話)-------只有登陸后的用戶才能訪問

admin.html:管理員頁面(只有一句話)----只有管理員才能訪問

login.html:登陸頁面(有表單)----------------不符合要求時,可以登錄

我們的目的,就是把這些權限管理起來。

?

靜態頁面代碼展示

?

下面展示一下這些頁面的代碼:

index.html:社區首頁(只有四個鏈接)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>首頁</title>
</head>
<body><h1>社區首頁</h1><ul><li><a th:href="@{/discuss}">帖子詳情</a></li><li><a th:href="@{/letter}">私信列表</a></li><li><a th:href="@{/loginpage}">登錄</a></li><li><a th:href="@{/loginpage}">退出</a></li></ul>
</body>
</html>

discuss.html:帖子詳情頁面(只有一句話)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>帖子</title>
</head>
<body><h1>帖子詳情頁面</h1>
</body>
</html>

letter.html:私信列表(只有一句話)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>私信</title>
</head>
<body><h1>私信列表頁面</h1>
</body>
</html>

admin.html:管理員頁面(只有一句話)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>管理員</title>
</head>
<body><h1>管理員專屬頁面</h1>
</body>
</html>

login.html:登陸頁面(有表單)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>登錄</title>
</head>
<body><h1>登錄社區</h1><form method="post" action="#"><p style="color:red;"><!--提示信息--></p><p>賬號:<input type="text" ></p><p>密碼:<input type="password" ></p><p>驗證碼:<input type="text" ></p><p><input type="submit" value="登錄"></p></form></body>
</html>

service層和user的操作

首先要處理我們的用戶user表,寫出獲取權限的方法。

實現UserDetails?接口,和接口定義的方法。

方法的作用我都已經標注在代碼里。

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;public class User implements UserDetails {private int id;private String username;private String password;private String salt;private String email;private int type;private int status;private String activationCode;private String headerUrl;private Date createTime;
/*
get/set方法
toSring方法
*/// true: 賬號未過期.@Overridepublic boolean isAccountNonExpired() {return true;}// true: 賬號未鎖定.@Overridepublic boolean isAccountNonLocked() {return true;}// true: 憑證未過期.@Overridepublic boolean isCredentialsNonExpired() {return true;}// true: 賬號可用.@Overridepublic boolean isEnabled() {return true;}// 返回用戶權限//我們有兩種用戶:1代表管理員,2代表普通用戶@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> list = new ArrayList<>();list.add(new GrantedAuthority() {@Overridepublic String getAuthority() {switch (type) {case 1:return "ADMIN";default:return "USER";}}});return list;}}

著重介紹一下getAuthorities方法:

它的返回值是一個權限集合,因為我們真實開發可能是這樣的:

用戶表:記錄了用戶類型(是普通用戶還是1級管理員、2級管理員、賣家買家等等)

權限表:記錄了每個角色有什么權限,比如普通用戶可以發帖評論點贊,管理員可以刪貼置頂等等。

最后我們通過用戶類型,查到多種權限,并且返回。

因為每種用戶有多種權限,所以getAuthorities方法的返回值是一個權限集合。,這個集合可以裝很多GrantedAuthority對象。

本代碼的集合只裝了一個權限對象,并且重寫了對應回去權限的方法。。


service層實現接口和對應方法。

import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.User;
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 UserService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;public User findUserByName(String username) {return userMapper.selectByName(username);}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return this.findUserByName(username);}
}

這個接口是要實現查找對應的用戶。

我們自己寫登錄邏輯的時候,一樣要這么做:用賬號(id)查到用戶,讀取密碼,看看用戶輸入的和在數據庫查到的是否相同,

security底層也是做了類似的事情,所以我們需要告訴他如何查找用戶。

如果我們之前寫過selectByName之類的方法,可以直接調用即可。

這里我把dao層和xml實現也給出來。

import com.nowcoder.community.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {User selectByName(String username);
}
<mapper namespace="com.community.dao.UserMapper"><sql id="selectFields">id, username, password, salt, email, type, status, activation_code, header_url, create_time</sql><select id="selectByName" resultType="User">select<include refid="selectFields"></include>from userwhere username = #{username}</select></mapper>

核心操作

書寫配置類統一管理。

有詳細的注釋。

通常我們需要書寫如下代碼:

忽略哪些資源?

認證

授權

package com.community.config;import com.community.entity.User;
import com.community.service.UserService;
import com.community.util.CommunityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@Overridepublic void configure(WebSecurity web) throws Exception {// 忽略靜態資源的訪問web.ignoring().antMatchers("/resources/**");}// AuthenticationManager: 認證的核心接口.// AuthenticationManagerBuilder: 用于構建AuthenticationManager對象的工具.// ProviderManager: AuthenticationManager接口的默認實現類.@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 內置的認證規則// auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));// 自定義認證規則// AuthenticationProvider: ProviderManager持有一組AuthenticationProvider,每個AuthenticationProvider負責一種認證.// 委托模式: ProviderManager將認證委托給AuthenticationProvider.auth.authenticationProvider(new AuthenticationProvider() {// Authentication: 用于封裝認證信息的接口,不同的實現類代表不同類型的認證信息.@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String username = authentication.getName();String password = (String) authentication.getCredentials();User user = userService.findUserByName(username);if (user == null) {throw new UsernameNotFoundException("賬號不存在!");}password = CommunityUtil.md5(password + user.getSalt());if (!user.getPassword().equals(password)) {throw new BadCredentialsException("密碼不正確!");}// principal: 主要信息; credentials: 證書; authorities: 權限;return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());}// 當前的AuthenticationProvider支持哪種類型的認證.@Overridepublic boolean supports(Class<?> aClass) {// UsernamePasswordAuthenticationToken: Authentication接口的常用的實現類.return UsernamePasswordAuthenticationToken.class.equals(aClass);}});}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 登錄相關配置http.formLogin().loginPage("/loginpage").loginProcessingUrl("/login").successHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.sendRedirect(request.getContextPath() + "/index");}}).failureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {request.setAttribute("error", e.getMessage());request.getRequestDispatcher("/loginpage").forward(request, response);}});// 退出相關配置http.logout().logoutUrl("/logout").logoutSuccessHandler(new LogoutSuccessHandler() {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.sendRedirect(request.getContextPath() + "/index");}});// 授權配置http.authorizeRequests().antMatchers("/letter").hasAnyAuthority("USER", "ADMIN").antMatchers("/admin").hasAnyAuthority("ADMIN").and().exceptionHandling().accessDeniedPage("/denied");// 增加Filter,處理驗證碼http.addFilterBefore(new Filter() {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;if (request.getServletPath().equals("/login")) {String verifyCode = request.getParameter("verifyCode");if (verifyCode == null || !verifyCode.equalsIgnoreCase("1234")) {request.setAttribute("error", "驗證碼錯誤!");request.getRequestDispatcher("/loginpage").forward(request, response);return;}}// 讓請求繼續向下執行.filterChain.doFilter(request, response);}}, UsernamePasswordAuthenticationFilter.class);// 記住我http.rememberMe().tokenRepository(new InMemoryTokenRepositoryImpl()).tokenValiditySeconds(3600 * 24).userDetailsService(userService);}
}

表單

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>登錄</title>
</head>
<body><h1>登錄社區</h1><form method="post" th:action="@{/login}"><p style="color:red;" th:text="${error}"><!--提示信息--></p><p>賬號:<input type="text" name="username" th:value="${param.username}"></p><p>密碼:<input type="password" name="password" th:value="${param.password}"></p><p>驗證碼:<input type="text" name="verifyCode"> <i>1234</i></p><p><input type="checkbox" name="remember-me"> 記住我</p><p><input type="submit" value="登錄"></p></form></body>
</html>

HomeController?

package com.nowcoder.community.controller;import com.nowcoder.community.entity.User;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@Controller
public class HomeController {@RequestMapping(path = "/index", method = RequestMethod.GET)public String getIndexPage(Model model) {// 認證成功后,結果會通過SecurityContextHolder存入SecurityContext中.Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (obj instanceof User) {model.addAttribute("loginUser", obj);}return "/index";}@RequestMapping(path = "/discuss", method = RequestMethod.GET)public String getDiscussPage() {return "/site/discuss";}@RequestMapping(path = "/letter", method = RequestMethod.GET)public String getLetterPage() {return "/site/letter";}@RequestMapping(path = "/admin", method = RequestMethod.GET)public String getAdminPage() {return "/site/admin";}@RequestMapping(path = "/loginpage", method = {RequestMethod.GET, RequestMethod.POST})public String getLoginPage() {return "/site/login";}// 拒絕訪問時的提示頁面@RequestMapping(path = "/denied", method = RequestMethod.GET)public String getDeniedPage() {return "/error/404";}}

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444917.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444917.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444917.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

nginx python webpy 配置安裝

安裝webpy$ wget http://webpy.org/static/web.py-0.34.tar.gz$ tar xvzf web.py-0.34.tar.gz$ cd web.py-0.34$ sudo python setup.py install安裝 Fluphttp://www.saddi.com/software/flup/dist/flup-1.0.2.tar.gz$ wget http://www.saddi.com/software/flup/dist/flup-1.0.2…

PaperNotes(7)-GANs模式坍塌/訓練不穩定

GANs-模式坍塌-訓練不穩定1.訓練不穩定問題相關文章1.1 DCGAN1.2Big-GAN1.3WGAN 、WGAN-GP、SN-WGAN1.4其他工作2.模式坍塌問題相關文章2.1 MAD-GAN2.2 Unrolled GAN2.3 DRAGAN2.4 D2GAN2.5 InfoGAN2.6 Deligan2.7 EBGAN2.8 Maximum Entropy Generators for Energy-Based Model…

thinkphp框架起步認識

先看看thinkphp的文檔吧&#xff1a;這是我在網上找的一個不錯的鏈接地址&#xff0c;對自己有用&#xff0c;同時相信對讀者也有用吧。 http://doc.thinkphp.cn/manual/class.html ThinkPHP 跨模塊調用操作方法&#xff08;A方法與R方法&#xff09; 跨模塊調用操作方法 前面說…

leetcode403 青蛙過河

一只青蛙想要過河。 假定河流被等分為 x 個單元格&#xff0c;并且在每一個單元格內都有可能放有一石子&#xff08;也有可能沒有&#xff09;。 青蛙可以跳上石頭&#xff0c;但是不可以跳入水中。 給定石子的位置列表&#xff08;用單元格序號升序表示&#xff09;&#xff…

PaperNotes(8)-Stein Variational Gradient Descent A General Purpose Bayesian Inference Algorithm

通用貝葉斯推理算法-Stein Variational Gradient DescentAbstract1 Introduction2 Background3 Variational Inference Using Smooth Transforms3.1 Stein Operator as the Derivative of KL Divergence定理3.1引理3.23.2 Stein Variational Gradient Descent4 Related Works5 …

thinkphp的增刪改查

ThinkPHP 添加數據 add 方法 ThinkPHP 內置的 add 方法用于向數據表添加數據&#xff0c;相當于 SQL 中的 INSERT INTO 行為。ThinkPHP Insert 添加數據添加數據 add 方法是 CURD&#xff08;Create,Update,Read,Delete / 創建,修改,讀取,刪除&#xff09;中的 Create 的實現&a…

leetcode115 不同的子序列

給定一個字符串 S 和一個字符串 T&#xff0c;計算在 S 的子序列中 T 出現的個數。 一個字符串的一個子序列是指&#xff0c;通過刪除一些&#xff08;也可以不刪除&#xff09;字符且不干擾剩余字符相對位置所組成的新字符串。&#xff08;例如&#xff0c;"ACE" 是…

ThinkPHP 模板循環輸出 Volist 標簽

volist 標簽用于在模板中循環輸出數據集或者多維數組。volist 標簽在模塊操作中&#xff0c;select() 方法返回的是一個二維數組&#xff0c;可以用 volist 直接輸出&#xff1a;<volist name"list" id"vo"> 用 戶 名&#xff1a;{$vo[username]}&l…

MachineLearning(9)-最大似然、最小KL散度、交叉熵損失函數三者的關系

最大似然-最小KL散度-最小化交叉熵損失-三者的關系問題緣起&#xff1a;給定一組數據(x1,x2,...,xm)(x^1,x^2,...,x^m)(x1,x2,...,xm),希望找到這組數據服從的分布。此種情況下&#xff0c;分布規律用概率密度p(x)表征。 問題歸處&#xff1a;如果能夠建模/近似建模p(x)&#…

ThinkPHP redirect 頁面重定向使用詳解與實例

ThinkPHP redirect 方法ThinkPHP redirect 方法可以實現頁面的重定向&#xff08;跳轉&#xff09;功能。redirect 方法語法如下&#xff1a;$this->redirect(string url, array params, int delay, string msg) 參數說明&#xff1a;url 必須&#xff0c;重定向的 URL 表達…

PaperNotes(9)-Learning deep energy model: contrastive divergence vs. Amortized MLE

Learning deep energy model: contrastive divergence vs. Amortized MLEabstract1 Introduction2 Background2.1 stein variational gradient descent2.2 learning energy model**contrastive Divergence**abstract 受SVGD算法的啟發,本文提出兩個算法用于從數據中學習深度能…

windows下的gvim配置

首要任務是下載安裝Gvim7.3 。 安裝完后&#xff0c;gvim菜單中文出現亂碼&#xff0c;在_vimrcset文件中增加&#xff1a; " 配置多語言環境,解決中文亂碼問題 if has("multi_byte") " UTF-8 編碼 set encodingutf-8 set termencodingutf…

leetcode104 二叉樹的最大深度

給定一個二叉樹&#xff0c;找出其最大深度。 二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。 說明: 葉子節點是指沒有子節點的節點。 示例&#xff1a; 給定二叉樹 [3,9,20,null,null,15,7]&#xff0c; 3 / \ 9 20 / \ 15 7 返回它的最大深度…

C++的安全類型轉換的討論

關于強制類型轉換的問題,很多書都討論過,寫的最詳細的是C++ 之父的《C++的設計和演化》。最好的解決方法就是不要使用C風格的強制類型轉換,而是使用標準C++的類型轉換符:static_cast, dynamic_cast。標準C++中有四個類型轉換符:static_cast、dynamic_cast、reinterpret_ca…

PaperNotes(10)-Maximum Entropy Generators for Energy-Based Models

Maximum Entropy Generators for Energy-Based ModelsAbstract1 Introduction2 Background3 Maximum Entropy Generators for Energy-Based Models4 Experiments5 Related Work6 Conclusion7 AcknowledgementsAbstract 由于對數似然梯度的難以計算&#xff0c;能量模型的最大似…

leetcode105 前序中序遍歷序列構造二叉樹

根據一棵樹的前序遍歷與中序遍歷構造二叉樹。 注意: 你可以假設樹中沒有重復的元素。 例如&#xff0c;給出 前序遍歷 preorder [3,9,20,15,7] 中序遍歷 inorder [9,3,15,20,7] 返回如下的二叉樹&#xff1a; 3 / \ 9 20 / \ 15 7 思路&#xff1a; 1、…

c++的虛擬繼承 的一些思考吧

虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。如:類D繼承自類B1、B2,而類B1、B2都繼承自類A,因此在類D中兩次出現類A中的變量和函數。為了節省內存空間,可以將B1、B2對A的繼承定義為虛擬繼承,而A就成了虛擬基類。實現的代碼如下: class A class …

對于linux socket與epoll配合相關的一些心得記錄

對于linux socket與epoll配合相關的一些心得記錄 沒有多少高深的東西&#xff0c;全當記錄&#xff0c;雖然簡單&#xff0c;但是沒有做過測試還是挺容易讓人糊涂的int nRecvBuf32*1024;//設置為32Ksetsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int))…

leetcode144 二叉樹的前序遍歷

給定一個二叉樹&#xff0c;返回它的 前序 遍歷。 示例: 輸入: [1,null,2,3] 1 \ 2 / 3 輸出: [1,2,3] 進階: 遞歸算法很簡單&#xff0c;你可以通過迭代算法完成嗎&#xff1f; 思路&#xff1a;模仿遞歸的思路壓棧即可。 /*** Definition for a bi…

AJAX大總結

1、AJAX概述 1.1 什么是AJAX AJAX&#xff08;Asynchronous Javascript And XML&#xff09;翻譯成中文就是“異步Javascript和XML”。即使用Javascript語言與服務器進行異步交互&#xff0c;傳輸的數據為XML&#xff08;當然&#xff0c;傳輸的數據不只是XML&#xff09;。 …