精通 Spring Boot 系列文(12)
閱讀全文,約 18?分鐘

Spring Boot 的安全管理
1、Spring Security 是啥?
Spring Security 是 Spring 的一個安全模塊,它很強(qiáng)大,但使用特別復(fù)雜。在安全管理這個領(lǐng)域,之前還有一個 Shiro 是比較受歡迎的,對于大部分的應(yīng)用,Shiro 用得也比較成熟。Spring Boot 現(xiàn)在為 Spring Security 提供了自動化配置方案,用起來非常方便,所以大家慢慢就選擇使用了 Spring Security 了。
最近,很多安全管理技術(shù)棧的組合長這樣的:Spring Boot/Spring Cloud + Spring Security。
安全框架有兩大主要操作:認(rèn)證(Authentication)和授權(quán)(Authorization)。
2、Spring Security 簡單使用
如何配置 Spring Security?非常簡單,我們直接在類上繼承 WebSecurityConfigurerAdapter 適配器即可,然后再用 @EnableWebSecurity 注解,再重寫
configure() 方法來配置對應(yīng)的安全信息。
我們還需要了解兩個事情:用戶認(rèn)證、用戶授權(quán)
2.1 用戶認(rèn)證
主要通過在?
configureGlobal(AuthenticationManagerBuilder amb) 方法完成用戶認(rèn)證,然后通過?
AuthenticationManagerBuilder 的 inMemoryAuthentication() 方法來添加用戶,和用戶的權(quán)限。
2.2 用戶授權(quán)
主要通過 configure(HttpSecurity hs) 方法,完成用戶授權(quán),然后 HttpSecurity 的 authorizeRequests() 方法能設(shè)置多個 macher 節(jié)點(diǎn)來聲明執(zhí)行順序,這樣用戶就能夠訪問多個 URL 模式了。
當(dāng)你匹配了對應(yīng)的請求路徑之后,然后再執(zhí)行安全處理。
Spring Security 的安全處理方法:
anyRequest:匹配所有路徑
access:可以訪問,當(dāng) Spring EL 的結(jié)果為 ture
anonymous:匿名可訪問
denyAll:用戶不能訪問
fullyAuthenticated:用戶完全認(rèn)證可訪問
hasAnyAuthority:參數(shù)代表權(quán)限,列出來任何一個的可訪問
hasAnyRole:參數(shù)代表角色,列出來任何一個的可訪問
hasAuthority:參數(shù)代表權(quán)限,列出來的可訪問
hasIpAddress:參數(shù)代表 IP 地址,匹配的可訪問
hasRole:參數(shù)角色,列出來的可訪問
permitAll:用戶可以任意訪問
rememberMe:允許通過 remember-me 登錄的用戶訪問
authenticated:用戶登錄后可訪問
3、Spring Security 簡單案例
1)編輯 pom.xml 文件
<project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
?????????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">
????<modelVersion>4.0.0modelVersion>
????<groupId>com.nxgroupId>
????<artifactId>springbootdataartifactId>
????<version>1.0-SNAPSHOTversion>
????<parent>
????????<groupId>org.springframework.bootgroupId>
????????<artifactId>spring-boot-starter-parentartifactId>
????????<version>2.2.6.RELEASEversion>
????????<relativePath/>
????parent>
????<properties>
????????<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
????????<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
????????<java.version>1.8java.version>
????properties>
??<dependencies>
??????
??????<dependency>
????????<groupId>org.springframework.bootgroupId>
????????<artifactId>spring-boot-starter-webartifactId>
????dependency>
??????
??????<dependency>
????????<groupId>org.springframework.bootgroupId>
????????<artifactId>spring-boot-starter-thymeleafartifactId>
????dependency>
????
??????<dependency>
????????<groupId>org.springframework.bootgroupId>
????????<artifactId>spring-boot-starter-securityartifactId>
????dependency>
????<dependency>
??????<groupId>junitgroupId>
??????<artifactId>junitartifactId>
??????<scope>testscope>
????dependency>
??dependencies>
project>
2)創(chuàng)建 NXPasswordEncoder 認(rèn)證邏輯類
public?class?NXPasswordEncoder?implements?PasswordEncoder{
????@Override
????public?String?encode(CharSequence?arg0)?{
????????return?arg0.toString();
????}
????@Override
????public?boolean?matches(CharSequence?arg0,?String?arg1)?{
????????return?arg1.equals(arg0.toString());
????}
}
3)創(chuàng)建 AppSecurityConfigurer 密碼器
目前,Spring Security 的密碼存儲格式為:{id}encodedPassword,其中 id 是用來找到對應(yīng)的 PasswordEncoder,encodedPassword 用來指原始密碼經(jīng)過加密之后的密碼。當(dāng)我們想自定義密碼器,必須實(shí)現(xiàn) PasswordEncoder 接口。
@Configuration
public?class?AppSecurityConfigurer?extends?WebSecurityConfigurerAdapter{
????//?注入認(rèn)證處理類,處理不同用戶跳轉(zhuǎn)到不同的頁面
????@Autowired
????AppAuthenticationSuccessHandler?appAuthenticationSuccessHandler;
????//?用戶授權(quán)操作?
????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????http.authorizeRequests()
????????//?需要過濾靜態(tài)資源
????????.antMatchers("/login","/css/**","/js/**","/img/*").permitAll()?
????????.antMatchers("/",?"/home").hasRole("USER")
????????.antMatchers("/admin/**").hasAnyRole("ADMIN",?"DBA")
????????.anyRequest().authenticated()
????????.and()
????????.formLogin().loginPage("/login").successHandler(appAuthenticationSuccessHandler)
????????.usernameParameter("loginName").passwordParameter("password")
????????.and()
????????.logout().permitAll()
????????.and()
????????.exceptionHandling().accessDeniedPage("/accessDenied");
????}
????//?用戶認(rèn)證操作
????@Autowired
????public?void?configureGlobal(AuthenticationManagerBuilder?auth)?throws?Exception?{
????????//?需要密碼編碼器
????????auth.inMemoryAuthentication().passwordEncoder(new?NXPasswordEncoder()).withUser("nx").password("888888").roles("USER");
???????auth.inMemoryAuthentication().passwordEncoder(new?NXPasswordEncoder()).withUser("admin").password("admin").roles("ADMIN","DBA");
????}
}
4)創(chuàng)建 AppAuthenticationSuccessHandler 認(rèn)證成功處理類
@Component
public?class?AppAuthenticationSuccessHandler?extends?SimpleUrlAuthenticationSuccessHandler{
????//?通過?RedirectStrategy?對象負(fù)責(zé)所有重定向事務(wù)
????private?RedirectStrategy?redirectStrategy?=?new?DefaultRedirectStrategy();
????//?重寫?handle?方法,通過?RedirectStrategy?對象重定向到指定的?url
????@Override
????protected?void?handle(HttpServletRequest?request,?HttpServletResponse?response,
????????????Authentication?authentication)
????????????throws?IOException?{
????????//?通過?determineTargetUrl?方法返回需要跳轉(zhuǎn)的?url?
????????String?targetUrl?=?determineTargetUrl(authentication);
????????redirectStrategy.sendRedirect(request,?response,?targetUrl);
????}
????//?從 Authentication 對象中提取角色提取當(dāng)前登錄用戶的角色,并根據(jù)其角色返回適當(dāng)?shù)?URL。
????protected?String?determineTargetUrl(Authentication?authentication)?{
????????String?url?=?"";
????????//?獲取當(dāng)前登錄用戶的角色權(quán)限集合
????????Collection?extends?GrantedAuthority>?authorities?=?authentication.getAuthorities();
????????List?roles?=?new?ArrayList();
????????for?(GrantedAuthority?a?:?authorities)?{
????????????roles.add(a.getAuthority());
????????}
????????//?判斷不同角色跳轉(zhuǎn)到不同的url
????????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?roles) ?{
????????if?(roles.contains("ROLE_USER"))?{
????????????return?true;
????????}
????????return?false;
????}
????private?boolean?isAdmin(List?roles) ?{
????????if?(roles.contains("ROLE_ADMIN"))?{
????????????return?true;
????????}
????????return?false;
????}
????public?void?setRedirectStrategy(RedirectStrategy?redirectStrategy)?{
????????this.redirectStrategy?=?redirectStrategy;
????}
????protected?RedirectStrategy?getRedirectStrategy()?{
????????return?redirectStrategy;
????}
}
5)創(chuàng)建 NXController 控制器
@Controller
public?class?NXController?{
????@RequestMapping("/")
????public?String?index()?{
????????return?"index";
????}
?????@RequestMapping(value?=?"/login")
????public?String?login()?{
????????return?"login";
????}
????@RequestMapping("/home")
????public?String?homePage(Model?model)?{
????????model.addAttribute("user",?getUsername());
????????model.addAttribute("role",?getAuthority());
????????return?"home";
????}
????@RequestMapping(value?=?"/admin")
????public?String?adminPage(Model?model)?{
????????model.addAttribute("user",?getUsername());
????????model.addAttribute("role",?getAuthority());
????????return?"admin";
????}
????@RequestMapping(value?=?"/dba")
????public?String?dbaPage(Model?model)?{
????????model.addAttribute("user",?getUsername());
????????model.addAttribute("role",?getAuthority());
????????return?"dba";
????}
????@RequestMapping(value?=?"/accessDenied")
????public?String?accessDeniedPage(Model?model)?{
????????model.addAttribute("user",?getUsername());
????????model.addAttribute("role",?getAuthority());
????????return?"accessDenied";
????}
???@RequestMapping(value="/logout")
????public?String?logoutPage?(HttpServletRequest?request,?HttpServletResponse?response)?{
???????//?Authentication是一個接口,表示用戶認(rèn)證信息
????????Authentication?auth?=?SecurityContextHolder.getContext().getAuthentication();
????????//?如果用戶認(rèn)知信息不為空,注銷
????????if?(auth?!=?null){????
????????????new?SecurityContextLogoutHandler().logout(request,?response,?auth);
????????}
????????//?重定向到login頁面
????????return?"redirect:/login?logout";
????}
????private?String?getUsername(){
????????//?從SecurityContex中獲得Authentication對象代表當(dāng)前用戶的信息
????????String?username?=?SecurityContextHolder.getContext().getAuthentication().getName();
????????System.out.println("username?=?"?+?username);
????????return?username;
????}
????private?String?getAuthority(){
????????//?獲得Authentication對象,表示用戶認(rèn)證信息。
????????Authentication?authentication?=?SecurityContextHolder.getContext().getAuthentication();
????????List?roles?=?new?ArrayList();
????????for?(GrantedAuthority?a?:?authentication.getAuthorities())?{
????????????roles.add(a.getAuthority());
????????}
????????System.out.println("role?=?"?+?roles);
????????return?roles.toString();
????}
}
最后,大家可以找一套前端頁面,測試一下即可,非常簡單的。

Java后端編程
更多Java推文,關(guān)注公眾號
