SpringSecurity之Web权限方案

目录

springsecurity之Web权限方案-用户认证

用户认证即设置登录用户名和密码

第一种方式:通过配置文件application.properties

spring.security.user.name=user
spring.security.user.password=123456

第二种方式:通过配置类

  1. 继承WebSecurityConfigurerAdapter

  2. 重写configure(AuthenticationManagerBuilder auth)方法

  3. 将加密器注入容器

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 用auth来设置登录的用户名和密码
        // inMemoryAuthentication放入内存中
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("czs");
        auth.inMemoryAuthentication().withUser("czs").password(password).roles("admin");
    }

    /**
     * 报错There is no PasswordEncoder mapped for the id "null"
     * 因为springsecurity加密的时候如果不传使用那种加密器进行加密,springsecurity不知道使用哪种加密器进行解密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

第三种方式:自定义实现类(重点)

springsecurity在认证过程中,会先去配置文件和配置类中找用户名和密码的相关信息,如果没有设置就会去UserDetailsServie接口中找设置用户名和密码的方式

  1. 创建配置类,设置使用哪个UserDetailsServie实现类,并将其注入到spring中
  2. 编写实现类,返回User对象,User对象有用户名密码和操作权限

第一步操作:创建配置类,设置使用哪个UserDetailsServie实现类,并将其注入到spring中

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 编写实现类注入
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 设置自定义的userDetailsService实现类及用到的加密器类型
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    /**
     * 报错There is no PasswordEncoder mapped for the id "null"
     * 因为springsecurity加密的时候如果不传使用那种加密器进行加密,springsecurity不知道使用哪种加密器进行解密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

第二步操作:编写实现类,返回User对象,User对象有用户名密码和操作权限

AuthorityUtils.commaSeparatedStringToAuthorityList权限工具类的逗号分割字符串到授权列表

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User("czs", new BCryptPasswordEncoder().encode("123456"), authorities);
    }
}

查询数据库实现用户认证

整合Mybatis-Plus实现,核心代码重写loadUserByUsername即上述的第三种方式:自定义实现类整合了Mybatis-Plus

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        com.edu.security.entity.User user = userService.getOne(new QueryWrapper<com.edu.security.entity.User>().eq("username", s));

        if (StringUtils.isEmpty(user)) {
            // 数据库中没有用户名 认证失败
            // 执行认证失败的方法
            throw new UsernameNotFoundException("用户不存在");
        }
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorities);
    }
}

自定义用户登录页面

在配置类中实现相关配置

重写configure(HttpSecurity http)方法

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 关闭csrf防护
    http.csrf().disable();
    // 自定义直接编写的登录页面
    http.formLogin()
            .loginPage("/login.html")// 登录页面设置
            .loginProcessingUrl("/user/login") // 登录访问路径
            .defaultSuccessUrl("/test/index") // 登录成功之后,跳转路径
            .permitAll();
    // 定义哪些路径需要认证访问
    http.authorizeRequests()
            .antMatchers("/", "/test/hello", "/user/login") // 设置哪些路径可以直接访问,不需要认证
            .permitAll().anyRequest().authenticated();
}

登录页面提交的用户名密码参数name值必须是usernamepassword,原因是UsernamePasswordAuthenticationFilter这个过滤链只会接受表单提交上来的usernamepassword的值

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

springsecurity之Web权限方案-用户授权

基于权限访问控制

hasAuthority:如果当前的主体具有指定的权限,则返回true否则返回false

SpringSecurity之Web权限方案

hasAnyAuthority:如果当前的主体有任何一个给定的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true

SpringSecurity之Web权限方案

在配置类中设置当前访问地址有哪些权限

@Override
protected void configure(HttpSecurity http) throws Exception {
	....
    http.authorizeRequests()
            .antMatchers("/test/admin")
            .hasAuthority("admin") // 当前登录用户,只有具有admin权限才可以访问这个路径
            .anyRequest().authenticated();
    
    http.authorizeRequests()
            .antMatchers("/test/admin")
            .hasAnyAuthority("admin,user") // 当前登录用户,有admin,user其中一个权限即可访问
            .anyRequest().authenticated();
}

在UserDetailsService中给返回的User对象设置权限

....
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorities);

有权限就能成功访问,没有权限则返回403

SpringSecurity之Web权限方案

基于角色访问控制

hasRole:如果用户具备给定角色的允许访问,如果当前主体具有指定的角色,则返回true,否则出现403

SpringSecurity之Web权限方案

hasAnyRole:表示用户具备任何一个条件都可以访问

SpringSecurity之Web权限方案

在配置类中设置当前访问地址有哪些角色可以访问

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 关闭csrf防护
    http.csrf().disable();
	...
        
    // 定义哪些路径需要认证访问
    http.authorizeRequests()
            .antMatchers("/", "/test", "/user/login") // 设置哪些路径可以直接访问,不需要认证
            .permitAll()
            .and()
            .authorizeRequests()
            .antMatchers("/test/admin")
            .hasRole("admin") // 当前登录用户,只有具有admin角色才可以访问
            .anyRequest().authenticated();
    
    // 定义哪些路径需要认证访问
    http.authorizeRequests()
            .antMatchers("/", "/test", "/user/login") // 设置哪些路径可以直接访问,不需要认证
            .permitAll()
            .and()
            .authorizeRequests()
            .antMatchers("/test/admin")
            .hasAnyRole("admin,user") // 当前登录用户,有admin,user其中一个角色即可访问
            .anyRequest().authenticated();
}

在UserDetailsService中给返回的User对象设置权限

注意,角色权限要加上**ROLE_**前缀

....
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,user");
return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorities);

自定义403页面

如果不自定义403页面,系统默认

SpringSecurity之Web权限方案

配置类中配置自定义403页面

http.exceptionHandling().accessDeniedPage()

@Override
protected void configure(HttpSecurity http) throws Exception {
	....
    // 配置自定义403页面
    http.exceptionHandling().accessDeniedPage("/unauth.html");
}

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

使用注解进行认证授权

使用@Secured注解先要开启注解功能

@EnableGlobalMethodSecurity(securedEnabled = true)

SpringSecurity之Web权限方案

使用@Prexxx或者@Postxxx注解先要开启注解功能

@EnableGlobalMethodSecurity(prePostEnabled = true)

SpringSecurity之Web权限方案

@Secured:判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀"ROLE_"

@GetMapping("test/admin")
@Secured({"ROLE_admin","ROLE_super"})
public R testAdmin() {
    return R.ok().data("data", "只有admin用户可以访问");
}

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

SpringSecurity之Web权限方案

@GetMapping("test/admin")
@PreAuthorize("hasAnyAuthority('admin')")
public R testAdmin() {
    return R.ok().data("data", "只有admin用户可以访问");
}

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

@GetMapping("test/admin")
@PostAuthorize("hasAnyAuthority('admin')")
public R testAdmin() {
    return R.ok().data("data", "只有admin用户可以访问");
}

@PostFilter:权限验证之后,对数据进行过滤,留下用户名是admin1的数据

表达式中的filterObject引用的是返回值LIst中的某一个元素

@GetMapping("test/admin")
@PostAuthorize("hasRole('ROLE_admin')")
@PostFilter("filterObject.username=='admin1'")
public R testAdmin() {
    ArrayList<Object> list = new ArrayList<>();
    list.add(new UserInfo(11,"admin1","1111"));
    list.add(new UserInfo(22,"admin2","2222"));
    return R.ok().data("data", list);
}

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

@GetMapping("test/admin")
@PreAuthorize("hasRole('ROLE_admin')")
@PreFilter("filterObject.username=='admin1'")
public R testAdmin() {
    ArrayList<Object> list = new ArrayList<>();
    list.add(new UserInfo(11,"admin1","1111"));
    list.add(new UserInfo(22,"admin2","2222"));
    return R.ok().data("data", list);
}

用户注销

在配置类中添加退出映射地址

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ....
        // logoutUrl退出登录请求路径 logoutSuccessUrl退出登录成功跳转的路径
        http.logout().logoutUrl("/logout"). logoutSuccessUrl("/logout/index").permitAll();
    }

SpringSecurity之Web权限方案

登录成功之后,访问/logout退出登录成功后自动跳转到logout/index,再次去访问需要权限的请求需要重新登录才能访问

自动登录

实现原理

第一次访问的时候,用户登录认证请求UsernamePasswordAuthenticationFilter过滤器,认证成功后调用successfulAuthentication方法由RememberMeService做相应的处理将会向浏览器和数据库分别发送一份用户的token信息。再次访问的时候,请求RememberMeAuthenticationFilter过滤器获取cookie信息,调用check方法拿着cookie信息到数据库进行比对,如果查询到对应的信息,则认证成功进行登录

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

功能实现

创建数据表JdbcTokenRepositoryImpl中的CREATE_TABLE_SQL脚本

create table persistent_logins (
    username varchar(64) not null, 
    series varchar(64) primary key, 
    token varchar(64) not null, 
    last_used timestamp not null)

配置类中注入数据源,配置操作数据库对象

/**
 * 注入数据源
 */
@Autowired
private DataSource dataSource;

/**
 * 配置对象
 * 
 * 注入JdbcTokenRepositoryImpl上层接口更好 new的是具体实现对象
 * 将数据源注入进JdbcTokenRepositoryImpl
 */
@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    // 自动生成数据表,如果手动创建就不用开启
    // jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}

配置类中配置自动登录

configure(HttpSecurity http)

@Override
protected void configure(HttpSecurity http) throws Exception {
	....
    // 记住我相关设置 设置token数据库对象 设置cookie的有效时长 设置查询数据库的userDetailsService
   http.rememberMe()
        .tokenRepository(persistentTokenRepository())
        .tokenValiditySeconds(60)
        .userDetailsService(userDetailsService);
}

登录页面添加remember-me复选框

此处:name属性值必须为remember-me,不能改为其他值,spring security指定的

SpringSecurity之Web权限方案

cookie和数据库已经存入加密信息

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

测试,登录成功之后,关闭浏览器,重新访问需要权限的请求,请求成功

CSRF功能

简介

跨站请求伪造(英语:Cross-site request forgery),也被称为one-click、attack或者session riding,通常缩写为CSRF或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。

从 Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF 会针对 PATCH,POST,PUT和DELETE方法进行防护。

使用方法

引入依赖 对thymeleaf添加springsecurity标签支持

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

在登录页面添加一个隐藏域

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />

SpringSecurity之Web权限方案

底层原理

生成csrfToken保存到HttpSession或者Cookie中CsrfFilter过滤器

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

SpringSecurity之Web权限方案

上一篇:2021BTAJ面试真题详解,郑州java工资一般多少


下一篇:SpringSecurity如何实现加密和解码?100%好评!