Java中Spring的Security使用詳解
Spring Security
在web應(yīng)用開發(fā)中,安全無疑是十分重要的,選擇Spring Security來保護web應(yīng)用是一個非常好的選擇。
Spring Security 是spring項目之中的一個安全模塊,可以非常方便與spring項目無縫集成。
特別是在spring boot項目中加入spring security更是十分簡單。
本篇我們介紹spring security,以及spring security在web應(yīng)用中的使用。
一個例子入門
假設(shè)我們現(xiàn)在創(chuàng)建好了一個springboot的web應(yīng)用,有一個控制器如下:
@Controller public class AppController { @RequestMapping("/hello") @ResponseBody String home() { return "Hello ,spring security!"; } }
我們啟動應(yīng)用,假設(shè)端口是8080,那么當我們在瀏覽器訪問//localhost:8080/hello的時候可以在瀏覽器看到Hello ,spring security!。
加入spring security 保護應(yīng)用
此時,/hello是可以自由訪問。假設(shè),我們需要具有某個角色的用戶才能訪問的時候,我們可以引入spring security來進行保護。加入如下maven依賴,并重啟應(yīng)用:
<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>
我們可以發(fā)現(xiàn),這里有個form 。action=”/login”,這個/login是spring security提供的。
form表單提交了三個數(shù)據(jù):
- username 用戶名
- password 密碼
- _csrf CSRF保護方面的內(nèi)容
說明spring security 已經(jīng)起作用了。這時,它為你生成了賬號和密碼,在內(nèi)存中。
但是,我們不可能只是這么使用它,我們?nèi)绾瓮ㄟ^訪問數(shù)據(jù)庫來登錄,驗證權(quán)限呢?
接下來通過一個實例繼續(xù)深入。
demo項目權(quán)限介紹
我們通過一個很簡單的項目來認識一下Spring Security。
- index.html:社區(qū)首頁(只有四個鏈接)----------任何人都可以訪問
- discuss.html:帖子詳情頁面(只有一句話)------任何人都可以訪問
- letter.html:私信列表(只有一句話)-------只有登陸后的用戶才能訪問
- admin.html:管理員頁面(只有一句話)----只有管理員才能訪問
- login.html:登陸頁面(有表單)----------------不符合要求時,可以登錄
我們的目的,就是把這些權(quán)限管理起來。
靜態(tài)頁面代碼展示
下面展示一下這些頁面的代碼:
index.html:社區(qū)首頁(只有四個鏈接)
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首頁</title> </head> <body> <h1>社區(qū)首頁</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:帖子詳情頁面(只有一句話)
<!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>登錄社區(qū)</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表,寫出獲取權(quán)限的方法。
實現(xiàn)UserDetails 接口,和接口定義的方法。
方法的作用我都已經(jīng)標注在代碼里。
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: 賬號未過期. @Override public boolean isAccountNonExpired() { return true; } // true: 賬號未鎖定. @Override public boolean isAccountNonLocked() { return true; } // true: 憑證未過期. @Override public boolean isCredentialsNonExpired() { return true; } // true: 賬號可用. @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方法:
它的返回值是一個權(quán)限集合,因為我們真實開發(fā)可能是這樣的:
- 用戶表:記錄了用戶類型(是普通用戶還是1級管理員、2級管理員、賣家買家等等)
- 權(quán)限表:記錄了每個角色有什么權(quán)限,比如普通用戶可以發(fā)帖評論點贊,管理員可以刪貼置頂?shù)鹊取?/li>
最后我們通過用戶類型,查到多種權(quán)限,并且返回。
因為每種用戶有多種權(quán)限,所以getAuthorities方法的返回值是一個權(quán)限集合。,這個集合可以裝很多GrantedAuthority對象。
本代碼的集合只裝了一個權(quán)限對象,并且重寫了對應(yīng)回去權(quán)限的方法。。
service層實現(xiàn)接口和對應(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); } }
這個接口是要實現(xiàn)查找對應(yīng)的用戶。
我們自己寫登錄邏輯的時候,一樣要這么做:用賬號(id)查到用戶,讀取密碼,看看用戶輸入的和在數(shù)據(jù)庫查到的是否相同,
security底層也是做了類似的事情,所以我們需要告訴他如何查找用戶。
如果我們之前寫過selectByName之類的方法,可以直接調(diào)用即可。
這里我把dao層和xml實現(xiàn)也給出來。
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)一管理。
有詳細的注釋。
通常我們需要書寫如下代碼:
忽略哪些資源?
認證
授權(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)資源的訪問 web.ignoring().antMatchers("/resources/**"); } // AuthenticationManager: 認證的核心接口. // AuthenticationManagerBuilder: 用于構(gòu)建AuthenticationManager對象的工具. // ProviderManager: AuthenticationManager接口的默認實現(xiàn)類. @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 內(nèi)置的認證規(guī)則 // auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345")); // 自定義認證規(guī)則 // AuthenticationProvider: ProviderManager持有一組AuthenticationProvider,每個AuthenticationProvider負責一種認證. // 委托模式: ProviderManager將認證委托給AuthenticationProvider. auth.authenticationProvider(new AuthenticationProvider() { // Authentication: 用于封裝認證信息的接口,不同的實現(xià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("賬號不存在!"); } 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()); } // 當前的AuthenticationProvider支持哪種類型的認證. @Override public boolean supports(Class<?> aClass) { // UsernamePasswordAuthenticationToken: Authentication接口的常用的實現(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,處理驗證碼 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", "驗證碼錯誤!"); request.getRequestDispatcher("/loginpage").forward(request, response); return; } } // 讓請求繼續(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> 賬號:<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) { // 認證成功后,結(jié)果會通過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"; } }
到此這篇關(guān)于Java中Spring的Security使用詳解的文章就介紹到這了,更多相關(guān)Spring的Security內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析Java類和數(shù)據(jù)結(jié)構(gòu)中常用的方法
下面小編就為大家?guī)硪黄獪\析Java類和數(shù)據(jù)結(jié)構(gòu)中常用的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09springboot項目使用SchedulingConfigurer實現(xiàn)多個定時任務(wù)的案例代碼
這篇文章主要介紹了springboot項目使用SchedulingConfigurer實現(xiàn)多個定時任務(wù),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01Java SSM整合開發(fā)統(tǒng)一結(jié)果封裝詳解
這篇文章主要介紹了Java SSM整合開發(fā)實現(xiàn)統(tǒng)一結(jié)果封裝,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08Java?ThreadPoolExecutor線程池有關(guān)介紹
這篇文章主要介紹了Java?ThreadPoolExecutor線程池有關(guān)介紹,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09