SpringSecurity入門
什么是SpringSecurity
Spring Security 的前身是 Acegi Security ,是 Spring 項目組中用來提供安全認證服務的框架。
(https://projects.spring.io/spring-security/) Spring Security 為基于J2EE企業應用軟件提供了全面安全服務。特別
是使用領先的J2EE解決方案-Spring框架開發的企業軟件項目。人們使用Spring Security有很多種原因,不過通常吸
引他們的是在J2EE Servlet規范或EJB規范中找不到典型企業應用場景的解決方案。特別要指出的是他們不能再
WAR 或 EAR 級別進行移植。這樣,如果你更換服務器環境,就要,在新的目標環境進行大量的工作,對你的應用
系統進行重新配置安全。使用Spring Security 解決了這些問題,也為你提供很多有用的,完全可以指定的其他安
全特性。安全包括兩個主要操作。
- “認證”,是為用戶建立一個他所聲明的主體。主題一般式指用戶,設備或可以在你系統中執行動作的其他系
統。(簡單來說:系統認為用戶是否能登錄)
- “授權”,指的是一個用戶能否在你的應用中執行某個操作,在到達授權判斷之前,身份的主題已經由身份驗證
過程建立了。(簡單來說:系統判斷用戶是否有權限去做某些事情)
這些概念是通用的,不是Spring Security特有的。在身份驗證層面,Spring Security廣泛支持各種身份驗證模式,
這些驗證模型絕大多數都由第三方提供,或則正在開發的有關標準機構提供的,例如 Internet Engineering Task
Force.作為補充,Spring Security 也提供了自己的一套驗證功能。
Spring Security 目前支持認證一體化如下認證技術: HTTP BASIC authentication headers (一個基于IEFT RFC 的
標準) HTTP Digest authentication headers (一個基于IEFT RFC 的標準) HTTP X.509 client certi?cate exchange
(一個基于IEFT RFC 的標準) LDAP (一個非常常見的跨平臺認證需要做法,特別是在大環境) Form-based
authentication (提供簡單用戶接口的需求) OpenID authentication Computer Associates Siteminder JA-SIG
Central Authentication Service (CAS,這是一個流行的開源單點登錄系統) Transparent authentication context
propagation for Remote Method Invocation and HttpInvoker (一個Spring遠程調用協議)
用戶登錄認證
表結構分析與創建
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GnUk2Dvg-1692278828416)(day03_springboot綜合案例.assets/image-20210516220645141.png)]
-- 用戶表
CREATE TABLE users(id VARCHAR(32) PRIMARY KEY,email VARCHAR(50) UNIQUE NOT NULL,username VARCHAR(50),PASSWORD VARCHAR(100),phoneNum VARCHAR(20),STATUS INT
);-- 角色表
CREATE TABLE role(id VARCHAR(32) PRIMARY KEY,roleName VARCHAR(50) ,roleDesc VARCHAR(50)
);-- 用戶角色關聯表
CREATE TABLE users_role(userId VARCHAR(32),roleId VARCHAR(32),PRIMARY KEY(userId,roleId),FOREIGN KEY (userId) REFERENCES users(id),FOREIGN KEY (roleId) REFERENCES role(id)
);-- 資源權限表
CREATE TABLE permission(id VARCHAR(32) PRIMARY KEY,permissionName VARCHAR(50) ,url VARCHAR(50)
);-- 角色權限關聯表
CREATE TABLE role_permission(permissionId VARCHAR(32),roleId VARCHAR(32),PRIMARY KEY(permissionId,roleId),FOREIGN KEY (permissionId) REFERENCES permission(id),FOREIGN KEY (roleId) REFERENCES role(id)
);
創建類
創建UserInfo
@Data
public class UserInfo {private String id;private String username;private String email;private String password;private String phoneNum;private int status;private String statusStr;private List<Role> roles;
}
創建Role
@Data
public class Role {private String id;private String roleName;private String roleDesc;private List<Permission> permissions;private List<UserInfo> users;
}
創建Permission
@Data
public class Permission {private String id;private String permissionName;private String url;private List<Role> roles;
}
Spring Security數據庫認證底層
在Spring Security中如果想要使用數據進行認證操作,有很多種操作方式,這里我們介紹使用UserDetails、
UserDetailsService來完成操作。
-
UserDetails
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled(); }
UserDatails是一個接口,我們可以認為UserDetails作用是于封裝當前進行認證的用戶信息,但由于其是一個
接口,所以我們可以對其進行實現,也可以使用Spring Security提供的一個UserDetails的實現類User來完成
操作,Ctrl+Alt+B 查找接口實現類
以下是User類的部分代碼
public class User implements UserDetails, CredentialsContainer {private String password;private final String username;private final Set<GrantedAuthority> authorities;private final boolean accountNonExpired; //帳戶是否過期private final boolean accountNonLocked; //帳戶是否鎖定private final boolean credentialsNonExpired; //認證是否過期 private final boolean enabled; //帳戶是否可用 }
-
UserDetailsService
public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
上面將UserDetails與UserDetailsService做了一個簡單的介紹,那么我們具體如何完成Spring Security的數據庫認
證操作,我們通過用戶管理中用戶登錄來完成Spring Security的認證操作。
用戶登錄流程分析
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KG7Pu1wO-1692278828417)(assets/image-20201012180237425.png)]
登錄認證
添加依賴
<!--SpringSecurity--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
Spring Security配置類
package cn.yanqi.config;import cn.yanqi.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;// @EnableGlobalMethodSecurity(jsr250Enabled = true) //開啟jsr250注解
// @EnableGlobalMethodSecurity(securedEnabled = true) //開啟secured注解
// @EnableGlobalMethodSecurity(prePostEnabled = true) //開啟表達式注解
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate UserService userService;@Overrideprotected void configure(HttpSecurity http) throws Exception {//自定義表單登錄頁面http.formLogin()//指定登錄頁面.loginPage("/to/login")//指定登錄請求.loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").successForwardUrl("/to/index").failureUrl("/to/failer").and().logout().logoutUrl("/logout").logoutSuccessUrl("/to/login").invalidateHttpSession(true) //是否清除session.and()//權限配置.authorizeRequests()//放行 登錄頁面.antMatchers("/to/login","/to/failer").permitAll()//放開 靜態資源.antMatchers("/css/**","/img/**","/js/**","/plugins/**").permitAll()//其他 資源需要登錄后訪問.anyRequest().authenticated().and()//禁用csrf.csrf().disable();//沒有權限http.exceptionHandling().accessDeniedPage("/to/403");}//認證的數據需要使用自定義的UserDetailsService@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}
編寫UserService
package cn.yanqi.service;import org.springframework.security.core.userdetails.UserDetailsService;
//繼承 UserDetailsService 重寫loadUserByUsername 完成認證
public interface UserService extends UserDetailsService {}
import cn.yanqi.travel.mapper.UserMapper;
import cn.yanqi.travel.pojo.Role;
import cn.yanqi.travel.pojo.UserInfo;
import cn.yanqi.travel.service.UserService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;@Service
public class UserServiceImpl implements UserService {@Resourceprivate UserMapper userMapper;@Resourceprivate PasswordEncoder passwordEncoder;/*** 認證--查詢用戶* @param s* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {UserInfo userInfo = this.userMapper.findUserByUserName(s);User user = new User(userInfo.getUsername(),userInfo.getPassword(),userInfo.getStatus() == 0 ? false : true,//校驗用戶是否開啟true, //帳號是否過期 不過期true, //證號 不過期true, //帳號 不鎖定getAuthority(userInfo.getRoles()));System.out.println("用戶:"+userInfo.getUsername());System.out.println("=======================");return user;}/*** 認證--查詢用戶對應的角色*/private List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {List<SimpleGrantedAuthority> list = new ArrayList<>();for(Role role : roles){System.out.println("對應角色:"+role.getRoleName());list.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));}return list;}
}
編寫UserMapper
public interface UserMapper {/*** 通過用戶名查詢用戶* @param s* @return*/UserInfo findUserByUserName(String s);
}
編寫UserMapper.xml
<!--登錄認證:通過用戶名查詢用戶--><resultMap id="userresultMap" type="UserInfo" autoMapping="true"><id property="id" column="id"/><collection property="roles" ofType="Role" javaType="List" autoMapping="true"><id property="id" column="rid"/></collection></resultMap><select id="findUserByUserName" resultMap="userresultMap">SELECT*,r.id ridFROMusers u,role r,users_role urWHEREu.id = ur.userId ANDr.id = ur.roleId ANDu.username = #{s}</select>
注意事項:如果登錄認證提交出現405,是因為通用頁面跳轉是@GetMapping, Security的登錄后臺跳轉需要post請求
把通用頁面跳轉改為@RequestMapping(“{page}”)即可
測試
登錄認證-把users表中的status狀態修改 0和1進行測試
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-64TQouaV-1692278828419)(day03_springboot綜合案例.assets/image-20210517223706124.png)]
權限控制
Spring Security配置類
// @EnableGlobalMethodSecurity(jsr250Enabled = true) //開啟jsr250注解
// @EnableGlobalMethodSecurity(securedEnabled = true) //開啟secured注解
// @EnableGlobalMethodSecurity(prePostEnabled = true) //開啟表達式注解
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {//Spring Security配置類
}
代碼實現
基于方法級別權限控制,有三種方式
/*** 查詢所有產品* @param page* @param size* @return*/// @RolesAllowed({"ADMIN","USER"}) // JSR-250注解// @RolesAllowed("ADMIN") // JSR-250注解// @Secured("ROLE_ADMIN") // Secured注解// @PreAuthorize("authentication.principal.username == 'jack'")//只有jack才可以訪問@RequestMapping("findAll")public String findAll( Model model,@RequestParam(value = "page",defaultValue = "1") Integer page,@RequestParam(value = "size",defaultValue = "5") Integer size){PageHelper.startPage(page,size);List<Product> productList = this.productService.findAll();PageInfo pageInfo = new PageInfo(productList);model.addAttribute("pageInfo", pageInfo);return "product-list";}
uestMapping(“findAll”)
public String findAll( Model model,
@RequestParam(value = “page”,defaultValue = “1”) Integer page,
@RequestParam(value = “size”,defaultValue = “5”) Integer size){
PageHelper.startPage(page,size);List<Product> productList = this.productService.findAll();PageInfo pageInfo = new PageInfo(productList);model.addAttribute("pageInfo", pageInfo);return "product-list";
}