通过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 启用注解@PreAuthorize@ @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

猜你喜欢

转载自blog.csdn.net/qq_36027670/article/details/84861184