SpringBoot Security 动态权限入门

废话

本案例是以最简单最简单的方式实现动态权限配置,摒弃各种花里胡哨的代码。

动态权限主要需要实现两个功能:

1、Url访问权限的动态设置

2、用户本身具备的权限动态设置

基础逻辑主要就是用Security作为登录、权限校验,权限允许则访问,权限不允许则提示权限不足。

一、准备工作

1、一个简单的SpringBoot工程

2、引入

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

3、数据源

正常需要三张数据表,用户表、权限表、Url表,然后你就需要读取数据库、连Mybatis、JDBC、Redis巴拉巴拉的,这个跟主题无关,此处用三个写死的类替代就好。

(1)、权限管理类,枚举出来本系统的全部权限

@Service
public class RoseService {
    public final String ADMIN = "ADMIN";
    public final String USER = "USER";
}

(2)、Url管理类,指定那些Url需要哪些权限才能访问

@Service
public class UrlService {

    @Autowired
    RoseService roseService;

    public String[] getRose(String url) {
        if (url.equals("user")) {
            return new String[]{roseService.USER};
        }
        if (url.equals("admin")) {
            return new String[]{roseService.ADMIN, roseService.USER};
        }
        return new String[]{};
    }
}

(3)、用户对象,必须实现Security的UserDetails的6个方法。


@Component
public class UserEntity implements UserDetails {
    private String username;
    private String password;
    private String[] roses;

    /**
     * 获取当前用户对象所具有的角色信息
     */

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (String rose : roses) {
            authorities.add(new SimpleGrantedAuthority(rose));
        }
        return authorities;
    }

    /**
     * 获取当前用户对象的密码
     */

    @Override
    public String getPassword() {
        return password;
    }

    /**
     * 获取当前用户对象的用户名
     */

    @Override
    public String getUsername() {
        return username;
    }

    /**
     * 当前账户是否未过期
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 当前账户是否未锁定
     */

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 当前账户密码是否未过期
     */

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 当前账户是否可用
     */

    @Override
    public boolean isEnabled() {
        return true;
    }


    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setRose(String[] roses) {
        this.roses = roses;
    }
}

(4)、用户管理类,必须实现Security的UserDatailsService的loadUserByUsername方法,用户登录的时候Security会自动调用该方法

@Service
public class UserService implements UserDetailsService {
    /**
     * UserDetailsService接口中的实现方法(用户登陆时自动调用)
     *
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity user = getUser(username);
        if (null == user) {
            throw new UsernameNotFoundException("账户不存在!");
        }
        return user;
    }


    public UserEntity getUser(String username) {
        UserEntity user = null;
        if (username.equals("admin")) {
            user = new UserEntity();
            user.setUsername("admin");
            user.setPassword(new BCryptPasswordEncoder().encode("123456"));
            user.setRose(new String[]{"ADMIN"});
        }
        if (username.equals("user")) {
            user = new UserEntity();
            user.setUsername("user");
            user.setPassword(new BCryptPasswordEncoder().encode("123456"));
            user.setRose(new String[]{"USER"});
        }
        return null;
    }
}

4、一个简单的Controller

设定是这样的,open-不需要权限,admin支持ADMIN权限,user支持ADMIN、USER权限。

@RestController
public class TestController {

    @RequestMapping(value = "open")
    public Object open() {
        return "open";
    }

    @RequestMapping(value = "admin")
    public Object admin() {
        return "admin";
    }

    @RequestMapping(value = "user")
    public Object user() {
        return "user";
    }
}

二、开搞

需要准备最少三个类。

1、WebSecurityFilterInvocationSecurityMetadataSource

用于根据Url获取到该Url需要什么样的权限才能访问

@Component
public class WebSecurityFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    UrlService urlService;

    private static Map<String, String[]> roseMap;

    /**
     * 将所有的url的权限信息都拿出来,当数据有变化时,记得再更新一次
     * <p>
     * 在getAttributes()方法内不可直接使用Mapper,因为Security先于Spring加载,此时还没有注入,会报空指针异常
     * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次,依赖注入初始化后会自动执行该方法
     */
    @PostConstruct
    private List<String> getRoses() {
        System.out.println("Url权限数据已加载");
        roseMap = new HashMap<>();
        roseMap.put("/admin", urlService.getRose("admin"));
        roseMap.put("/user", urlService.getRose("user"));
        return null;
    }

    /**
     * 通过当前请求的URL获取所需要的权限信息
     *
     * @param o(FilterInvocation) 用于获取当前请求的url
     * @return 当前请求url所需的角色
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        String url = ((FilterInvocation) o).getRequestUrl();
        System.out.println("当前请求的Url:" + url);
        String[] roses = roseMap.get(url);
        return SecurityConfig.createList(roses);
    }

    /**
     * 返回所有定义好的权限资源
     * Spring Security在启动时会校验相关配置是否正确,如果不需要校验,直接返回null
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        System.out.println("返回所有定义好的权限资源");
        return null;
    }

    /**
     * 是否支持校验
     */
    @Override
    public boolean supports(Class<?> aClass) {
        System.out.println("是否支持校验");
        return true;
    }
}

2、WebSecurityAccessDecisionManger

用于权限校验,对比传过来的Url需要的权限和当前用户拥有的权限去判定当前用户对当前Url是否有权访问

@Component
public class WebSecurityAccessDecisionManger implements AccessDecisionManager {


    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {

        //如果authentication是UsernamePasswordAuthenticationToken实例,说明当前用户已登录。
        if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
            System.out.println("----------------> 请先登录!");
            throw new AccessDeniedException("请先登录");
        }
        Collection<? extends GrantedAuthority> auths = authentication.getAuthorities();

        System.out.println("Url需要的权限:" + collection.toString());
        System.out.println("用户当前的权限:" + auths.toString());

        for (ConfigAttribute configAttribute : collection) {
            //循环校验,有权限满足就证明是可以访问的
            for (GrantedAuthority authority : auths) {
                if (configAttribute.getAttribute().equals(authority.getAuthority())) {
                    return;
                }
            }
        }
        System.out.println("----------------> 权限不足!");
        throw new AccessDeniedException("权限不足!");
    }

    /**
     * 是否支持校验
     */
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        System.out.println("configAttribute是否支持校验");
        return true;

    }

    /**
     * 是否支持校验
     */
    @Override
    public boolean supports(Class<?> aClass) {
        System.out.println("aClass是否支持校验");
        return true;

    }
}

3、WebSecurityConfig

最核心的Security配置类

@Component
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/500").permitAll()//不过滤
                .antMatchers("/403").permitAll()//不过滤
                .antMatchers("/404").permitAll()//不过滤
                //其他不过滤的Url,略
                .anyRequest() //任何其它请求
                .authenticated() //都需要身份认证
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        //自定义权限校验
                        object.setSecurityMetadataSource(new WebSecurityFilterInvocationSecurityMetadataSource());
                        object.setAccessDecisionManager(new WebSecurityAccessDecisionManger());
                        return object;
                    }
                })
                .and()
                // 开启表单登录,即登录页
                .formLogin()
                // 自定义登录页,未配置下启用默认登录页
                // .loginPage("/login_page")
                // 登录成功之后跳转的页面
                .loginProcessingUrl("/index")
                // 登录参数-用户名
                .usernameParameter("username")
                // 登录参数-密码
                .passwordParameter("password")
//                 登录成功回调函数-返回登陆成功的JSON信息
                .successHandler((request, response, authentication) -> {
                    System.out.println("-----登录成功-----> ");
                })
//                 登录失败回调函数-返回登陆失败的JSON信息
                .failureHandler((request, response, e) -> {

                    System.out.println("-----登录失败-----> ");
                })
                // 和登录相关的接口都不需要认证即可访问
                .permitAll()
                .and()
                // 开启注销登录配置
                .logout()
                // 配置注销登录请求URL,默认为 "/logout"
                .logoutUrl("/logout")
                // 是否清除身份认证信息,默认为true,表示清除
                .clearAuthentication(true)
                // 是否使session失效,默认为true
                .invalidateHttpSession(true)
                // 删除指定的Cookie信息,可以传入多个key
                .deleteCookies("JSESSIONID")
                // 注销回调函数,可以处理数据清除工作
                .addLogoutHandler((request, response, authentication) -> {
                    System.out.println("-----注销回调----->");
                    this.getCookieMsg(request);
                })
                // 注销成功回调函数
                .logoutSuccessHandler((request, response, authentication) -> {
                    System.out.println("-----注销成功----->");
                    // 注销成功后重定向到登录页
                    response.sendRedirect("/login");
                })
                .and()
                .csrf().disable();

//        http.addFilterBefore(authorizationSecurityInterceptor, FilterSecurityInterceptor.class);
    }

    /**
     * 自定义方法,获取Cookie信息
     */
    private void getCookieMsg(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (null == cookies || cookies.length < 1) {
            System.out.println("------> Cookie已全部清除!");
        } else {
            System.out.println("---Cookie---> ");
        }
    }

    //解决静态资源被拦截的问题
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/img/**");
        web.ignoring().antMatchers("/plugins/**");
    }

    /**
     * 密码加密方式,必须指定一种
     * 本例使用官方推荐的BCrypt强哈希函数,密钥迭代次数为2^strength,strength取值在4~31之间,默认为10
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(15);
    }

    /**
     * 认证设置(配置用户信息:登录名、登录密码、所属角色)
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值