SpringSecurity认证和授权


SpringSecurity认证和授权


本篇內容主要介紹Spring Security的认证和授权的应用配置及实现,认证和授权也是该框架的核心。

认证

配置文件设置用户名密码

spring.security.user.name=j 
spring.security.user.password=j

配置类实现

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("jason").password(encode).roles("ADMIN");
    }
}

遇到报错:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
 at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:254) ~[spring-security-core-5.4.2.jar:5.4.2]
 at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:202) ~[spring-security-core-5.4.2.jar:5.4.2]
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$LazyPasswordEncoder.matches(WebSecurityConfigurerAdapter.java:595) ~[spring-security-config-5.4.2.jar:5.4.2]

说明:因为PasswordEncoder并没有发现对应的实现类,因此在尝试解密时报错

解决:在配置类中声明实现类

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

通过UserDetailService实现类

实际生产中的方式。

简单实现,理解逻辑

  1. 编写UserDetailService的实现类,返回User对象,其中包括用户名、密码及权限

    @Service("userDetailsService")
    public class MyUserDetailService implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN");
            return new User("mary",new BCryptPasswordEncoder().encode("123"),list);
        }
    }
  2. 创建配置类,在其中声明UserDetailService的具体实现类

    @Configuration
    public class SecurityConfigNormal extends WebSecurityConfigurerAdapter {
        @Resource
        private UserDetailsService userDetailsService;

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }

        @Bean
        PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }

通过数据库查询

引入依赖(数据库相关的依赖),这里用的是mybatisPlus和mysql

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
         <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

创建mapper,修改MyUserDetailService方法

//UserMapper
@Repository
public interface UserMapper extends BaseMapper<Users{
}
//MyUserDetailService
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
    @Resource
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//        List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN");
//        return new User("mary",new BCryptPasswordEncoder().encode("123"),list);
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("username",s);
        Users user = userMapper.selectOne(wrapper);
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN");
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),list);
    }
}

修改启动类

@SpringBootApplication
@MapperScan("io.yunho.spring.security.learn.mapper")
public class SpringSecurityLearnApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityLearnApplication.classargs);
    }

}

修改配置文件

#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://mysql-host:3306/demo?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=xxxx

自定页面

这部分我们要接触一个非常关键的配置对象HttpSecurity

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        //退出
        http.logout().logoutUrl("/logout").
                logoutSuccessUrl("/test/hello").permitAll();

        //配置没有权限访问跳转自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin()   //自定义自己编写的登录页面
            .loginPage("/on.html")  //登录页面设置
            .loginProcessingUrl("/user/login")   //登录访问路径
            .defaultSuccessUrl("/success.html").permitAll()  //登录成功之后,跳转路径
                .failureUrl("/unauth.html")
            .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
                //当前登录用户,只有具有admins权限才可以访问这个路径
                //1 hasAuthority方法
               // .antMatchers("/test/index").hasAuthority("admins")
                //2 hasAnyAuthority方法 有任一权限即可访问
               // .antMatchers("/test/index").hasAnyAuthority("admins,manager")
                //3 hasRole方法   ROLE_sale  具备对应角色才可以访问
                .antMatchers("/test/index").hasRole("sale")

                .anyRequest().authenticated()//除上面定义之外的url全部需要鉴权
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60)//设置有效时长,单位秒
                .userDetailsService(userDetailsService);
               // .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
          // .and().csrf().disable();  //关闭csrf防护
    }

授权

SpringSecurity提供了基于角色的访问授权机制

hasAuthority 方法

从方法名上可以看出这个方法是限制具备相应权限的用户才可以访问。配置如下:

               //当前登录用户,只有具有admins权限才可以访问这个路径
                //1 hasAuthority方法
                .antMatchers("/test/index").hasAuthority("admins")

当没有权限时会返回403:

SpringSecurity认证和授权
image-20210422215833384

hasAnyAuthority方法

具有任一权限即可访问:

                //2 hasAnyAuthority方法 有任一权限即可访问
               .antMatchers("/test/index").hasAnyAuthority("admins,manager")

hasRole 方法

具备对应角色即可访问:

//配置类中
//3 hasRole方法   ROLE_sale  具备对应角色才可以访问
.antMatchers("/test/index").hasRole("SALE")
    
//授权类中
List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_SALE");

注意SpringSecurity会将角色前加ROLE_前缀,如果自己在角色前加’ROLE__’,会报错

.antMatchers("/test/index").hasRole("ROLE_SALE")
Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalArgumentException: role should not start with 'ROLE_' since it is automatically inserted. Got 'ROLE_SALE'

hasAnyRole 方法

类似hasAnyAuthority,略

自定义403页面

//SecurityConfigNormal
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置没有权限访问跳转自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
    }

常用注解

@Secured

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。使用注解先要开启注解功能!@EnableGlobalMethodSecurity(securedEnabled=true)

@SpringBootApplication
@MapperScan("io.yunho.spring.security.learn.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityLearnApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityLearnApplication.classargs);
    }

}
// 测试注解: 
@RequestMapping("testSecured"
@ResponseBody 
@Secured({"ROLE_normal","ROLE_admin"}) 
public String helloUser() 
 return "hello,user"

@PreAuthorize

@EnableGlobalMethodSecurity(prePostEnabled = true)

@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize可以将登录用户的roles/permissions参数传到方法中。

@RequestMapping("/preAuthorize"
@ResponseBody //@PreAuthorize("hasRole('ROLE_管理员')") 
@PreAuthorize("hasAnyAuthority('menu:system')"
public String preAuthorize()
    System.out.println("preAuthorize"); 
    return "preAuthorize"
}

@PostAuthorize

先开启注解功能:@EnableGlobalMethodSecurity(prePostEnabled = true)

@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.

@RequestMapping("/testPostAuthorize"
@ResponseBody 
@PostAuthorize("hasAnyAuthority('menu:system')"
public String preAuthorize()
    System.out.println("test--PostAuthorize"); 
    return "PostAuthorize"
}

@PostFilter

@PostFilter :权限验证之后对数据进行过滤 留下用户名是admin1的数据 表达式中的 filterObject 引用的是方法返回值List中的某一个元素

@RequestMapping("getAll"
@PreAuthorize("hasRole('ROLE_管理员')"
@PostFilter("filterObject.username == 'admin1'"
@ResponseBody 
public List<UserInfo> getAllUser()
    ArrayList<UserInfo> list = new ArrayList<>(); 
    list.add(new UserInfo(1l,"admin1","6666")); 
    list.add(new UserInfo(2l,"admin2","888")); 
    return list; 
}

@PreFilter

@PreFilter: 进入控制器之前对数据进行过滤

@RequestMapping("getTestPreFilter"
@PreAuthorize("hasRole('ROLE_管理员')"
@PreFilter(value = "filterObject.id%2==0"
@ResponseBody public List<UserInfo> getTestPreFilter(
 List<UserInfo> list)
{ list.forEach(t-> { 
  System.out.println(t.getId()+"t"+t.getUsername()); 
 }); 
 return list; 
}

文档:https://docs.spring.io/spring-security/site/docs/5.3.4.RELEASE/reference/html5/#el-access


原文始发于微信公众号(云户):SpringSecurity认证和授权

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/25754.html

(1)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!