SpringSecurity权限管理的详细使用

Spring Security是一个强大的、高度可定制的认证和访问控制框架,它专注于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于它可以很容易地扩展以满足定制需求。

功能特点:

  • 对身份验证和授权的全面和可扩展的支持

  • 防范诸如会话固定、点击劫持、跨站点请求伪造等攻击

  • Servlet API集成

  • 与Spring Web MVC的可选集成

SpringSecurity的详细使用

首先引入依赖:

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

 设置登陆的用户名和密码的四种方式:前三种方式仅做了解,可以跳过

方式一:基于配置文件方式

 方式二和方式三:基于重写(自定义)配置类方式

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    // 方式二:配置类继承WebSecurityConfigurerAdapter并重写方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        String encode = passwordEncoder().encode("123");  // 将密码加密
        // 定义账号,密码,权限
        auth.inMemoryAuthentication().withUser("lucy").password(encode).roles("admin");
    }
    // 修改认证参数方式三:方式一和方式二都是一样的写死的,我们应该从数据库获取账号密码,
    //                   要自定义UserDetailsService,具体使用在框架搭建中有详细说明
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    // 这个要有,框架要对密码做加密,不能是明文。
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(); // security框架自带的加密工具
    }
}

 框架搭建步骤和使用详解:

WebSecurityConfigurerAdapter:Security框架的核心配置类

UserDetailsService:查询数据库中用户名和密码,并返回

UserDetails:依赖于GrantedAuthority(权限),用于保存用户信息(账号、密码、角色、权限),并且有框架默认实现类 org.springframework.security.core.userdetails.User;

GrantedAuthority:装载单个权限,多个权限需要多个GrantedAuthority对象装载

步骤一: 自定义UserDetails接口实现类

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

// UserDetails:保存从数据库中获取的账号密码、聚合了GrantedAuthority(权限)
public class Account implements UserDetails {
    private String username;
    private String password;
    private Set<? extends GrantedAuthority> authorities = new HashSet<>();
    public Account() {}

    public Account(String username, String password, Set<? extends GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }
    // 返回权限集合
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }
    // 当前账户是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    // 当前账户是否未锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    // 当前账户密码是否未过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    // 当前账户是否可用
    @Override
    public boolean isEnabled() {
        return true;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }
}

   步骤二:自定义GrantedAuthority接口实现类

import org.springframework.security.core.GrantedAuthority;

// Permission 装载授权信息
public class Permission implements GrantedAuthority {
    private String value;
    public Permission() {}
    @Override
    public String getAuthority() {
        return this.value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}

  步骤三:自定义UserDetailsService接口实现类

import org.springframework.security.core.userdetails.UserDetailsService;

@Service
public class AccountDetailsService implements UserDetailsService {
    // 引入数据库操作Api,数据从数据库获取
    @Resource
    AccountDao accountDao;
    // loadUserByUsername接收账号,
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//        从数据库中查找用户数据以及对应权限,封装到Account中并返回,最终框架帮我们完成认证和授权
        // 为了方便理解这里写死了
        Account account = new Account();
        account.setUsername("123456@qq.com");
        account.setPassword("123456789");
        // 多个权限
        Permission permission1 = new Permission();
        permission1.setValue("qx:create_user");  // 此处字符串随意,这里只是命名规范
        Permission permission2 = new Permission();
        permission2.setValue("qx:updates_user");
        // 将多个权限装载到一个集合中,List也可以
        Set<Permission> set = new HashSet<>();
        set.add(permission1);
        set.add(permission2);
        // 将集合封装到account中,然后返回account
        account.setAuthorities(set);
        return account;
    }
}

  步骤四:自定义WebSecurityConfigurerAdapter 

package com.example.demo3.security;
// @Configuration有了以下任何一个注解都可以不用
// @EnableWebSecurity这个表示启用Web安全的注解,如果你已经是是一个web 项目,不需要使用此注解,
// Springboot的自动配置机制WebSecurityEnablerConfiguration已经引入了该注解
//@EnableGlobalMethodSecurity开启这个来判断用户对某个控制层的方法是否具有访问权限(见Controller层的@PreAuthorize)
// 这个注解很重要,如果没有这个注解,那么Controller里的方法将不受约束,只要登录成功就能访问,而无视是否有改权限。
// securedEnabled 开启@Secured注解扫描
// prePostEnabled 开启@PrePostEnabled注解扫描
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    // 修改认证参数方式四:会用到
    @Resource
    private UserDetailsService userDetailsService;
    // 核心配置方法
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 核心部分(以下配置的顺序不能随意):
        // 配置控制器URL的角色和权限
        http.authorizeRequests()
                .antMatchers("login","/loginout","/upload").permitAll() // 放行路径
                .anyRequest().authenticated() // 表示除了放行路径,其它路径都要认证授权

/*了解部分,后面用注解替代
                // 设置访问权限:
                // 只有具有admin权限才可以访问这个路径(Controller)
                .antMatchers("/handler1").hasAuthority("admin")
                // 只要包含admin1 权限就可以访问这个路径
                .antMatchers("/handler2").hasAnyAuthority(new String[]{"admin1","admin2"})
                // 必须同时具备多个 权限才可以访问这个路径
                .antMatchers().access("hasAuthority('qx1') and hasAuthority('qx2')")

                // 设置访问角色:
                // 只有具有给定角色才可以访问这个路径
                .antMatchers("/handler1").hasRole("role1")
                // 只要包含admin权限就可以访问这个路径
                .antMatchers("/handler2").hasAnyRole("role1","role2")
                // 同时具备多个角色才可以访问这个路径
                .antMatchers().access("hasRole('role1') and hasRole('role2') and hasRole('role3')")
*/
                .and()
                .formLogin()  // 访问非放行路径都会弹出表单(重定向到登陆页面)
                .and()
                // 修改认证参数方式四:
                // 这一步,告诉Security 框架,我们要用自己的UserDetailsService实现类
                // 来传递UserDetails对象给框架,框架会把这些信息生成Authorization对象使用
                .userDetailsService(userDetailsService);

/*了解部分
        // 登陆相关URL自定义
        http.formLogin()
                .loginPage("/login.html").permitAll()  // 自定义的登陆页面路径
                .usernameParameter("username").passwordParameter("password") // 账号密码
                .loginProcessingUrl("/user/login") // 登陆访问路径
                .defaultSuccessUrl("/test/index").permitAll() // 登陆成功后,跳转路径
                .failureUrl("/userLogin?error"); // 登陆失败跳转路径

        // 退出登陆url和退出登陆成功后跳转url
        http.logout()
                .logoutUrl("/logout")
                .clearAuthentication(true)  // 清除身份认证信息
                .invalidateHttpSession(true)  // 使session失效
                .logoutSuccessUrl("/index").permitAll();
        // 异常跳转页面
        http.exceptionHandling().accessDeniedPage("/error.html");
*/
        //关闭跨域和csrf保护,框架默认开启
        http.cors().disable()
            .csrf().disable();
    }
    // 这个要有,框架要对密码做加密,不能是明文,这里用的是框架自带的BCrypt加密器
    // 也可以自定义加密方式实现PasswordEncoder接口再return自定义实现类
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

步骤五:Controller层各种注解的用法:

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;

@RestController
@RequestMapping("/test")
public class UserHandler {
    private static Logger logger = LoggerFactory.getLogger(UserHandler.class);

    @RequestMapping("/t1")
    // 用户包含 某个 角色才能访问此方法
    @Secured({"ROLE_sale","ROLE_manager"})
    public String t1(){
        logger.info("成功访问t1");
        return "成功访问t1";
    }
    // 两个注解的区别:
    // @PreAuthorize:方法执行前进行权限验证
    // @PostAuthorize:方法返回后才进行权限验证

    // 注意t2和t3区别:
    // hasAuthority:用户同时拥有一个或多个权限才能访问此方法
    
    // hasAnyAuthority:用户包含 某个 权限才能访问此方法
    // 多个权限之间用 , 号隔开

    @RequestMapping("/t2")
    @PostAuthorize("hasAuthority('admins:admin1')")
    //@PostAuthorize("hasAnyAuthority('admin','boss')")
    public String t2(){
        logger.info("成功访问t2");
        return "成功访问t2";
    }

    @RequestMapping("/t3")
    @PreAuthorize("hasAuthority('admins:admin1')")
    //@PreAuthorize("hasAuthority('admin' and 'boss')")
    //@PreAuthorize("hasAnyAuthority('admin','boss')")
    public String t3(){
        logger.info("成功访问t3");
        return "成功访问t3";
    }

    // 注意t4和t5区别:
    // hasRole:用户必须同时拥有一个或多个角色才能访问此方法
    
    // hasAnyRole:用户包含 某个 角色才能访问此方法
    // 多个角色之间用 , 号隔开

    @RequestMapping("/t4")
    @PreAuthorize("hasRole('teacher')")
//    @PreAuthorize("hasRole('teacher' and 'boss')")
    //@PreAuthorize("hasAnyRole('teacher','role1','role2')")
    public String t4(){
        logger.info("成功访问t4");
        return "成功访问t4";
    }

    @RequestMapping("/t5")
    @PostAuthorize("hasRole('teacher')")
//    @PostAuthorize("hasRole('teacher' and 'boss')")
    //@PostAuthorize("hasAnyRole('teacher','role1','role2')")
    public String t5(){
        logger.info("成功访问t5");
        return "成功访问t5";
    }

    // 两个注解的区别:
    //@PreFilter:方法执行前处理
    //@PostFilter:方法返回后处理
    
    @RequestMapping("/6")
    // filterObject.name=='老一':表示只传入集合中 每个对象的sex属性值 == '男' 的所有对象。
    @PreFilter("filterObject.sex=='男'")  // 前置过滤器
    public String t6(@RequestBody List<User> userList){
        userList.forEach(t -> {
            System.out.println(t.getName()+t.getSex()+t.getAge());
        });
        logger.info("成功访问t6");
        return "成功访问t6";
    }

    @RequestMapping("/t7")
    // filterObject.name=='老一':表示只返回集合中 每个对象的name属性值 == '老一' 的所有对象。
    // 返回结果:User user1 = new User("老一", "男", 20);
    @PostFilter("filterObject.name=='老一'")  // 后置过滤器,方法返回后处理
    public List<User> t7(){
        logger.info("成功访问t7");
        User user1 = new User("老一", "男", 20);
        User user2 = new User("老二", "男", 20);
        ArrayList<User> list = new ArrayList<>();
        list.add(user1);
        list.add(user2);
        return list;
    }
}

以上步骤完成可以访问在浏览器访问/login,会出现框架返回的登陆页面,然后进行各种测试。

上面Controller中注解使用细节:

1.要让其中的注解被扫描到还需要到SecurityConfig中开启注解扫描配置如下:

 2.Role(角色)命名最终会加上前缀 “ ROLE_ ” ,所以我们数据库的命名也要加上前缀, 具体看下图源码:

官网链接:Spring Security

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

{添码行空}

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值