通過SpringSecurity實現一個許可權管理系統
一、許可權系統E-R圖
常用的許可權管理系統中包括四個實體表,分別是使用者表、角色表、許可權表、資源表,以及他們之間的三個聯絡表,實體表之間都是多對多的關係
備註:寫完了才發現角色表沒用到,請忽略
二、SpringSecurity
2.1 主要元件
(1)SecurityContextHolder:主要作用是提供訪問許可權的SecurityContext
(2)SecurityContext:用於儲存程式上下文安全相關資訊
(3)Authentication:儲存驗證資訊
(4)GrantedAuthority:授予使用者訪問許可權
(5)UserDetails:應用程式的DAO,用於構建Authentication的關鍵信物件
(6)UserDetailsService:通過使用者名稱或者唯一的欄位來建立一個UserDetails物件
2.2 過濾器
本例中主要用到了UsernamePasswordAuthenticationFilter和FilterSecurityInterceptor
(1)UsernamePasswordAuthenticationFilter必須設定一個AuthenticationManager物件,由
AuthenticationManager物件的authenticate()方法來授權一個請求物件,授權失敗則丟擲AuthenticationException異常,然後通過SpringSecuriy ExceptionHandler 處理器處理,最後通過DispatchServlet返回給客戶端授指定的資源
(2)FilterSecurityInterceptor包含兩個重要物件,FilterInvocationSecurityMetadataSource和AccessDecisionManager,FilterInvocationSecurityMetadataSource這個物件載入所有資源,AccessDecisionManager這個主要對使用者做資源的限制,對Request的處理
三、教程
3.1 SpringSecurityConfig,必須繼承WebSecurityConfigurerAdapter ,@EnableWebSecurity註解啟用SpringSecurity配置,@EnableGlobalMethodSecurity啟用SpringSecurity全域性配置,引數prePostEnabled=true 啟用註解@[email protected] @PostAuthorize,securedEnabled=true啟用註解@Secured,jsr250Enabled=true啟用註解@PermitAll,@RolesAllowed等註解,都可以作為訪問許可權控制註解,區別在於@Secured可以使用表示式已經返回的集合做許可權的控制;
@EnableWebSecurity
@Configuration
@Slf4j
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
@Resource//自定義UserDetailsService
private UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.securityInterceptor(filterSecurityInterceptor());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
//.accessDecisionManager(accessDecisionManager()) 此方法註冊一個url的攔截器
.and()
.formLogin()//登陸攔截器
.loginPage("/index")//自定義登陸頁面
.loginProcessingUrl("/login")//此處用的預設的處理登陸
.successForwardUrl("/loginSuccess")//登陸成功跳轉頁面,不要任何許可權
.defaultSuccessUrl("/loginSuccess")//登陸成功跳轉頁面,有權的跳轉到此頁面
.failureUrl("/loginFailure")//登陸失敗跳轉頁面
.permitAll()//許可權配置
.and()
.logout().permitAll()
.and()
.csrf().disable()
.exceptionHandling().accessDeniedPage("/accessDenied")//許可權拒絕url
;
}
@Bean//設定密碼加密方式
public PasswordEncoder passwordEncoder(){
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return StringUtils.equals(rawPassword.toString(),encodedPassword);
}
};
}
@Bean
public AccessDecisionManager accessDecisionManager(){
return new AccessDecisionManager() {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {//沒有相關資源不做許可權校驗
return;
}
//遍歷資源,如果擁有許可權,安全校驗通過
for (ConfigAttribute configAttribute : configAttributes){
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()){
if (StringUtils.equals(configAttribute.getAttribute(),grantedAuthority.getAuthority())){
return;
}
}
}
throw new AccessDeniedException("denied no right");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
};
}
@Resource
private PermissionMapper permissionMapper;
@Resource
private ClResourceMapper resourceMapper;
@Bean
public FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource(){
//載入所有資源
HashMap<String, Collection<ConfigAttribute>> map =new HashMap<>();
List<ClResource> resources = resourceMapper.findAll();
for(ClResource resource : resources){
List<ConfigAttribute> configAttributes = new ArrayList<>();
List<Permission> permissions = permissionMapper.findByResourceId(resource.getId());
for (Permission permission : permissions){
ConfigAttribute configAttribute = new SecurityConfig(permission.getName());
configAttributes.add(configAttribute);
}
map.put(resource.getPattern(),configAttributes);
}
return new FilterInvocationSecurityMetadataSource() {
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if (object instanceof FilterInvocation){
FilterInvocation fi = (FilterInvocation) object;
for (String pattern : map.keySet()){
AntPathRequestMatcher matcher = new AntPathRequestMatcher(pattern);
if (matcher.matches(fi.getHttpRequest())){
return map.get(pattern);//返回url匹配的資源
}
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
};
}
@Bean//配置FilterSecurityInterceptor
public FilterSecurityInterceptor filterSecurityInterceptor(){
FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
filterSecurityInterceptor.setSecurityMetadataSource(filterInvocationSecurityMetadataSource());
filterSecurityInterceptor.setObserveOncePerRequest(false);
return filterSecurityInterceptor;
}
3.2 UserService更具使用者名稱載入使用者及其許可權
@Service
public class UserService extends InMemoryUserDetailsManager {
@Resource
private ClUserMapper clUserMapper;
@Resource
private PermissionMapper permissionMapper;
//載入使用者和相關許可權
@Override
public UserDetails loadUserByUsername(String username) {
ClUser clUser = clUserMapper.findByUsername(username);
if (clUser == null){
throw new UsernameNotFoundException("使用者不存在");
}
List<Permission> permissions = permissionMapper.findByAdminUserId(clUser.getId().intValue());
List<GrantedAuthority> authorities = new ArrayList<>();
if (permissions != null && permissions.size() > 0){
for (Permission permission : permissions){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
authorities.add(grantedAuthority);
}
}
return new User(clUser.getUsername(),clUser.getPassword(),authorities) ;
}
}
3.3 測試介面
@RestController
public class TestController {
@Resource
private PermissionMapper permissionMapper;
@GetMapping("api/allPermission")
public List<Permission> allPermission(){
return permissionMapper.findAll();
}
@GetMapping("/hi/world")
public String hi(){
return "hello world";
}
@GetMapping("/test/test")
public String test(){
return "test";
}
@PreAuthorize("permitAll()")// 和下面 @PermitAll 作用一樣
@PermitAll
@GetMapping("/study/study")
public String study(){
return "study";
}
@RolesAllowed("ADMIN")
// @PreAuthorize("hasRole('ADMIN')")
@GetMapping("/learn/learn")
public String learn(){
return "learn";
}
}
四 演示結果
查詢userId=1的使用者許可權sql:
SELECTsys_user.username,sys_permission.name,sys_resource.patternFROMsys_userLEFTJOINsys_role_userONsys_user.id=sys_role_user.sys_user_idLEFTJOINsys_roleONsys_role_user.sys_role_id=sys_role.idLEFTJOINsys_permission_roleONsys_permission_role.role_id=sys_role.idLEFTJOINsys_permissionONsys_permission_role.permission_id=sys_permission.idLEFTJOINsys_permission_resourceONsys_permission_resource.permission_id=sys_permission.idLEFTJOINsys_resourceONsys_resource.id=sys_permission_resource.resource_idWHEREsys_user.id=1;
使用者admin 是USER角色,訪問的資源路徑是api/**和/hi
登陸成功頁面
請求/hi/world資源,是有許可權的
請求test/**資源
對於沒有許可權的資源不允許訪問
五 資料
1 官網文件:https://docs.spring.io/spring-security/site/docs/5.2.0.BUILD-SNAPSHOT/reference/htmlsingle/
2 參考部落格:https://www.cnblogs.com/softidea/p/7068149.html
3 原始碼:https://github.com/NapWells/java_framework_learn/tree/master/springsecuritydemo