Java中Spring的Security使用詳解
Spring Security
在web應(yīng)用開(kāi)發(fā)中,安全無(wú)疑是十分重要的,選擇Spring Security來(lái)保護(hù)web應(yīng)用是一個(gè)非常好的選擇。
Spring Security 是spring項(xiàng)目之中的一個(gè)安全模塊,可以非常方便與spring項(xiàng)目無(wú)縫集成。
特別是在spring boot項(xiàng)目中加入spring security更是十分簡(jiǎn)單。
本篇我們介紹spring security,以及spring security在web應(yīng)用中的使用。
一個(gè)例子入門
假設(shè)我們現(xiàn)在創(chuàng)建好了一個(gè)springboot的web應(yīng)用,有一個(gè)控制器如下:
@Controller
public class AppController {
@RequestMapping("/hello")
@ResponseBody
String home() {
return "Hello ,spring security!";
}
}我們啟動(dòng)應(yīng)用,假設(shè)端口是8080,那么當(dāng)我們?cè)跒g覽器訪問(wèn)//localhost:8080/hello的時(shí)候可以在瀏覽器看到Hello ,spring security!。
加入spring security 保護(hù)應(yīng)用
此時(shí),/hello是可以自由訪問(wèn)。假設(shè),我們需要具有某個(gè)角色的用戶才能訪問(wèn)的時(shí)候,我們可以引入spring security來(lái)進(jìn)行保護(hù)。加入如下maven依賴,并重啟應(yīng)用:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
再次訪問(wèn)/hello,我們可以得到一個(gè)http-basic的認(rèn)證彈窗,如下:

代碼如下:
<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>
我們可以發(fā)現(xiàn),這里有個(gè)form 。action=”/login”,這個(gè)/login是spring security提供的。
form表單提交了三個(gè)數(shù)據(jù):
- username 用戶名
- password 密碼
- _csrf CSRF保護(hù)方面的內(nèi)容
說(shuō)明spring security 已經(jīng)起作用了。這時(shí),它為你生成了賬號(hào)和密碼,在內(nèi)存中。

但是,我們不可能只是這么使用它,我們?nèi)绾瓮ㄟ^(guò)訪問(wèn)數(shù)據(jù)庫(kù)來(lái)登錄,驗(yàn)證權(quán)限呢?
接下來(lái)通過(guò)一個(gè)實(shí)例繼續(xù)深入。
demo項(xiàng)目權(quán)限介紹
我們通過(guò)一個(gè)很簡(jiǎn)單的項(xiàng)目來(lái)認(rèn)識(shí)一下Spring Security。
- index.html:社區(qū)首頁(yè)(只有四個(gè)鏈接)----------任何人都可以訪問(wèn)
- discuss.html:帖子詳情頁(yè)面(只有一句話)------任何人都可以訪問(wèn)
- letter.html:私信列表(只有一句話)-------只有登陸后的用戶才能訪問(wèn)
- admin.html:管理員頁(yè)面(只有一句話)----只有管理員才能訪問(wèn)
- login.html:登陸頁(yè)面(有表單)----------------不符合要求時(shí),可以登錄
我們的目的,就是把這些權(quán)限管理起來(lái)。
靜態(tài)頁(yè)面代碼展示
下面展示一下這些頁(yè)面的代碼:
index.html:社區(qū)首頁(yè)(只有四個(gè)鏈接)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首頁(yè)</title>
</head>
<body>
<h1>社區(qū)首頁(yè)</h1>
<ul>
<li><a th:href="@{/discuss}" rel="external nofollow" >帖子詳情</a></li>
<li><a th:href="@{/letter}" rel="external nofollow" >私信列表</a></li>
<li><a th:href="@{/loginpage}" rel="external nofollow" rel="external nofollow" >登錄</a></li>
<li><a th:href="@{/loginpage}" rel="external nofollow" rel="external nofollow" >退出</a></li>
</ul>
</body>
</html>discuss.html:帖子詳情頁(yè)面(只有一句話)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>帖子</title>
</head>
<body>
<h1>帖子詳情頁(yè)面</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>私信列表頁(yè)面</h1>
</body>
</html>admin.html:管理員頁(yè)面(只有一句話)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>管理員</title>
</head>
<body>
<h1>管理員專屬頁(yè)面</h1>
</body>
</html>login.html:登陸頁(yè)面(有表單)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登錄</title>
</head>
<body>
<h1>登錄社區(qū)</h1>
<form method="post" action="#">
<p style="color:red;">
<!--提示信息-->
</p>
<p>
賬號(hào):<input type="text" >
</p>
<p>
密碼:<input type="password" >
</p>
<p>
驗(yàn)證碼:<input type="text" >
</p>
<p>
<input type="submit" value="登錄">
</p>
</form>
</body>
</html>service層和user的操作
首先要處理我們的用戶user表,寫出獲取權(quán)限的方法。
實(shí)現(xiàn)UserDetails 接口,和接口定義的方法。
方法的作用我都已經(jīng)標(biāo)注在代碼里。
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: 賬號(hào)未過(guò)期.
@Override
public boolean isAccountNonExpired() {
return true;
}
// true: 賬號(hào)未鎖定.
@Override
public boolean isAccountNonLocked() {
return true;
}
// true: 憑證未過(guò)期.
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// true: 賬號(hào)可用.
@Override
public boolean isEnabled() {
return true;
}
// 返回用戶權(quán)限
//我們有兩種用戶:1代表管理員,2代表普通用戶
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (type) {
case 1:
return "ADMIN";
default:
return "USER";
}
}
});
return list;
}
}著重介紹一下getAuthorities方法:
它的返回值是一個(gè)權(quán)限集合,因?yàn)槲覀冋鎸?shí)開(kāi)發(fā)可能是這樣的:
- 用戶表:記錄了用戶類型(是普通用戶還是1級(jí)管理員、2級(jí)管理員、賣家買家等等)
- 權(quán)限表:記錄了每個(gè)角色有什么權(quán)限,比如普通用戶可以發(fā)帖評(píng)論點(diǎn)贊,管理員可以刪貼置頂?shù)鹊取?/li>
最后我們通過(guò)用戶類型,查到多種權(quán)限,并且返回。
因?yàn)槊糠N用戶有多種權(quán)限,所以getAuthorities方法的返回值是一個(gè)權(quán)限集合。,這個(gè)集合可以裝很多GrantedAuthority對(duì)象。
本代碼的集合只裝了一個(gè)權(quán)限對(duì)象,并且重寫了對(duì)應(yīng)回去權(quán)限的方法。。
service層實(shí)現(xiàn)接口和對(duì)應(yīng)方法
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 {
@Autowired
private UserMapper userMapper;
public User findUserByName(String username) {
return userMapper.selectByName(username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.findUserByName(username);
}
}這個(gè)接口是要實(shí)現(xiàn)查找對(duì)應(yīng)的用戶。
我們自己寫登錄邏輯的時(shí)候,一樣要這么做:用賬號(hào)(id)查到用戶,讀取密碼,看看用戶輸入的和在數(shù)據(jù)庫(kù)查到的是否相同,
security底層也是做了類似的事情,所以我們需要告訴他如何查找用戶。
如果我們之前寫過(guò)selectByName之類的方法,可以直接調(diào)用即可。
這里我把dao層和xml實(shí)現(xiàn)也給出來(lái)。
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 user
where username = #{username}
</select>
</mapper>核心操作
書寫配置類統(tǒng)一管理。
有詳細(xì)的注釋。
通常我們需要書寫如下代碼:
忽略哪些資源?
認(rèn)證
授權(quán)
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 {
@Autowired
private UserService userService;
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略靜態(tài)資源的訪問(wèn)
web.ignoring().antMatchers("/resources/**");
}
// AuthenticationManager: 認(rèn)證的核心接口.
// AuthenticationManagerBuilder: 用于構(gòu)建AuthenticationManager對(duì)象的工具.
// ProviderManager: AuthenticationManager接口的默認(rèn)實(shí)現(xiàn)類.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 內(nèi)置的認(rèn)證規(guī)則
// auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));
// 自定義認(rèn)證規(guī)則
// AuthenticationProvider: ProviderManager持有一組AuthenticationProvider,每個(gè)AuthenticationProvider負(fù)責(zé)一種認(rèn)證.
// 委托模式: ProviderManager將認(rèn)證委托給AuthenticationProvider.
auth.authenticationProvider(new AuthenticationProvider() {
// Authentication: 用于封裝認(rèn)證信息的接口,不同的實(shí)現(xiàn)類代表不同類型的認(rèn)證信息.
@Override
public 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("賬號(hào)不存在!");
}
password = CommunityUtil.md5(password + user.getSalt());
if (!user.getPassword().equals(password)) {
throw new BadCredentialsException("密碼不正確!");
}
// principal: 主要信息; credentials: 證書; authorities: 權(quán)限;
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
}
// 當(dāng)前的AuthenticationProvider支持哪種類型的認(rèn)證.
@Override
public boolean supports(Class<?> aClass) {
// UsernamePasswordAuthenticationToken: Authentication接口的常用的實(shí)現(xiàn)類.
return UsernamePasswordAuthenticationToken.class.equals(aClass);
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 登錄相關(guān)配置
http.formLogin()
.loginPage("/loginpage")
.loginProcessingUrl("/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/index");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
request.setAttribute("error", e.getMessage());
request.getRequestDispatcher("/loginpage").forward(request, response);
}
});
// 退出相關(guān)配置
http.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/index");
}
});
// 授權(quán)配置
http.authorizeRequests()
.antMatchers("/letter").hasAnyAuthority("USER", "ADMIN")
.antMatchers("/admin").hasAnyAuthority("ADMIN")
.and().exceptionHandling().accessDeniedPage("/denied");
// 增加Filter,處理驗(yàn)證碼
http.addFilterBefore(new Filter() {
@Override
public 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", "驗(yàn)證碼錯(cuò)誤!");
request.getRequestDispatcher("/loginpage").forward(request, response);
return;
}
}
// 讓請(qǐng)求繼續(xù)向下執(zhí)行.
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>登錄社區(qū)</h1>
<form method="post" th:action="@{/login}">
<p style="color:red;" th:text="${error}">
<!--提示信息-->
</p>
<p>
賬號(hào):<input type="text" name="username" th:value="${param.username}">
</p>
<p>
密碼:<input type="password" name="password" th:value="${param.password}">
</p>
<p>
驗(yàn)證碼:<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) {
// 認(rèn)證成功后,結(jié)果會(huì)通過(guò)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";
}
// 拒絕訪問(wèn)時(shí)的提示頁(yè)面
@RequestMapping(path = "/denied", method = RequestMethod.GET)
public String getDeniedPage() {
return "/error/404";
}
}
到此這篇關(guān)于Java中Spring的Security使用詳解的文章就介紹到這了,更多相關(guān)Spring的Security內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring Security LDAP實(shí)現(xiàn)身份驗(yàn)證的項(xiàng)目實(shí)踐
- 淺聊一下Spring?Security的使用方法
- springsecurity第三方授權(quán)認(rèn)證的項(xiàng)目實(shí)踐
- 使用Cloud?Studio構(gòu)建SpringSecurity權(quán)限框架(騰訊云?Cloud?Studio?實(shí)戰(zhàn)訓(xùn)練營(yíng))
- 解決Spring?Security集成knife4j訪問(wèn)接口文檔出現(xiàn)403的問(wèn)題
- 使用spring?security?BCryptPasswordEncoder接入系統(tǒng)
相關(guān)文章
淺析Java類和數(shù)據(jù)結(jié)構(gòu)中常用的方法
下面小編就為大家?guī)?lái)一篇淺析Java類和數(shù)據(jù)結(jié)構(gòu)中常用的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09
java redis 實(shí)現(xiàn)簡(jiǎn)單的用戶簽到功能
這篇文章主要介紹了java redis 實(shí)現(xiàn)簡(jiǎn)單的用戶簽到功能,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12
springboot項(xiàng)目使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)的案例代碼
這篇文章主要介紹了springboot項(xiàng)目使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01
springboot?自定義啟動(dòng)器的實(shí)現(xiàn)
本文主要介紹了springboot?自定義啟動(dòng)器的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
Java SSM整合開(kāi)發(fā)統(tǒng)一結(jié)果封裝詳解
這篇文章主要介紹了Java SSM整合開(kāi)發(fā)實(shí)現(xiàn)統(tǒng)一結(jié)果封裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Java?ThreadPoolExecutor線程池有關(guān)介紹
這篇文章主要介紹了Java?ThreadPoolExecutor線程池有關(guān)介紹,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09

