SpringSecurity基本原理

SpringSecurity本质

SpringSecurity本质是一个过滤器链:SpringSecurity底层有很多的过滤器。

底层有以下三个比较重要的过滤器:

FilterSecurityInterceptor:是一个方法级的权限过滤器,基本位于过滤器的最底部。

ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常。

UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中用户名和密码。

SpringSecurity过滤器方式

使用SpringSecurity需要先配置过滤器

SpringBoot帮助我们做了这一步。(ps:想了解可以自行了解)

 

开发过程中2个重要的接口

UserDetailService接口:

实际开发中,我们的账号密码都是从数据库中查出来的,所以我们就需要通过自定义逻辑控制认证逻辑。这就需要我们去实现UserDetailService接口。

过程:

  • 创建一个类继承UsernamePasswordAuthenticationFilter==>重写attempAuthentication方法,校验成功则调用successfulAuthentication,失败则调用unsuccessfulAuthentication
  • 创建类实现UserDetailService接口,编写查询数据库的过程,返回User对象,这个对象就是安全框架提供的对象

PasswordEncoder接口:

数据加密接口,用于User对象里面密码加密

WEB权限方案

认证

设置登录的用户名和密码

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

第二种方式:通过配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //加密
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("lucy").password(encode).roles("admin");
    }
    //必需
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

第三种方式:自定义编写实现类

1.创建配置类,设置使用哪个UserDetailService实现类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    //必需
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }
}

2.编写实现类,返回user对象,User对象有用户名密码和操作权限

 

@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User("mery",new BCryptPasswordEncoder().encode("123"),auths);
    }
}

整合Mybatis-Plus完成数据库操作

@Repository
public interface UsersMapper extends BaseMapper<Users> {
}

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

    @Autowired
    private UsersMapper usersMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用mapper里面的方法去查数据库
        QueryWrapper<Users> usersQueryWrapper = new QueryWrapper<>();
        usersQueryWrapper.eq("username",username);
        Users user = usersMapper.selectOne(usersQueryWrapper);

        //判断 没有则认证为空
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在!");
        }

        List<GrantedAuthority> auths =
                AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User(user.getUsername(),
                new BCryptPasswordEncoder().encode("123"),auths);
    }
}

启动类:

@SpringBootApplication
@MapperScan("com.mu.SpringSecurity.mapper")
public class SpringSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }

}

授权

自定义设置登录页面

1.在配置类种实现相关的配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    //必需
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }

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

@RestController
@RequestMapping("/test")
public class Test {

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

    @GetMapping("/index")
    public String index(){
        return "hello,index";
    }
}

2.创建相关的页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
      <form action="/user/login" method="post">
          用户名:<input type="text" name="username">
          <br/>
          密码:<input type="password" name="password">
          <br/>
          <input     type="submit" value="login">
      </form>
</body>
</html>

 

 

 

基于角色或权限进行访问控制

1基于权限进行访问控制

1.1.hasAuththority方法

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

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

Spring Security 中CSRF防护 spring security custom-filter_User

2.在UserDetailsService中,把返回User对象设置权限

Spring Security 中CSRF防护 spring security custom-filter_User_02

(ps:如没有权限,则会报403错误,)

该方法适用于当前主体具有指定权限.当主题有多个权限的时候,就不行了,就需要我们使用hasAnyAuthority方法.

1.2.hasAnyAuthority方法

针对多个权限或多个角色

Spring Security 中CSRF防护 spring security custom-filter_User_03

当前的主体中只要有admins,manager其中一个权限就可以访问

Spring Security 中CSRF防护 spring security custom-filter_ide_04

2基于角色进行访问控制

2.1.hasRole方法

如果用户具备给定角色就允许访问,否则出现403.

如果当前主题具有指定的角色,则返回true.

底层源码:

Spring Security 中CSRF防护 spring security custom-filter_bc_05

 

Spring Security 中CSRF防护 spring security custom-filter_bc_06

这里的hasRole("sale")的返回值就是ROLE_sale.

然后再在MyUserDetailsService中,添加权限

Spring Security 中CSRF防护 spring security custom-filter_bc_07

 

而在配置文件中则不需要添加这个"ROLE_"前缀,因为上述的底层代码会自动添加与之进行匹配.

Spring Security 中CSRF防护 spring security custom-filter_User_08

2.2.hasAnyRole方法

类似hasAnyAuthority方法,是针对用户拥有多个角色.

给用户添加角色:

Spring Security 中CSRF防护 spring security custom-filter_bc_09

修改配置文件:

Spring Security 中CSRF防护 spring security custom-filter_bc_10

 

自定义403页面

编写前端页面:

unauth.html

<body>
<h1>对不起,您没有访问权限!</h1>
</body>

在配置文件中直接配置:

http.exceptionHandling().accessDeniedPage("/unauth");

添加对应控制器:

@GetMapping("/unauth")
public String accessDenyPage(){
return "unauth";
}

 

注解使用

@Secured:

判断是否有这个角色,有这个角色可以访问方法,另外需要注意的是这里匹配的字符串需要添加前缀"ROLE_"。

1、在启动类/配置类上先开启注解功能!

@EnableGlobalMethodSecurity(securedEnabled=true) 

@SpringBootApplication
@MapperScan("com.mu.SpringSecurity.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }

}

2、在Controller的方法上使用这个注解

@GetMapping("update")
@Secured({"ROLE_sale","ROLE_manager"})
public String update(){
    return "hello,update";
}

3、在MyUserDetailService种设置用户的角色

List<GrantedAuthority> auths =
        AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");

@PreAutorize

进入方法前进行方法的验证。

1、在启动类上进行开启

@EnableGlobalMethodSecurity(prePostEnabled = true)

2、在方法上添加注解

@PreAuthorize("hasAnyAuthority('admins')")
@GetMapping("update")
public String update(){
    return "hello,update";
}

@PostAuthorize

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

1、先开启注解功能

@EnableGlobalMethodSecurity(prePostEnabled = true)

2、在方法上面加上该注解

@PostAuthorize("hasAnyAuthority('admin')")
    @GetMapping("update")
    public String update(){
        return "hello,update";
    }

用户注销

1、在配置类中添加退出配置

http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll

记住我(自动登录)功能实现

功能实现

1.创建数据库表

CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

//注入数据源
    @Autowired
    private DataSource dataSource;
//  配置对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
//        自动创建表
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

3.配置类配置自动登录

.and().rememberMe().tokenRepository(persistentTokenRepository())    //设置记住我
.tokenValiditySeconds(60)       //设置有效时长,单位秒
.userDetailsService(userDetailsService)

4.在登录页面添加复选框

<input type="checkbox" name="remember-me">自动登录

(ps:这里的name属性必须为"remember-me",不然系统无法找到)

CSRF理解

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

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

案例

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

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

关闭安全配置的类中的csrf

http.csrf().disable();