1. 程式人生 > >通過SpringSecurity實現一個許可權管理系統

通過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