Spring Security(1)—Web 权限方案

Spring Security(1)—Web 权限方案

官方网址:https://spring.io/projects/spring-security

概要

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security正是Spring家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

Spring Security重要核心功能:用户认证(Authentication)和用户授权 (Authorization)。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录 。

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户 所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以 进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的 权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

与Shiro的区别

  • Spring Security 优点:

1、Spring Security 基于Spring 开发,项目若使用 Spring 作为基础,配合 Spring Security 做权限更加方便,而 Shiro 需要和 Spring 进行整合开发;

2、Spring Security 功能比 Shiro 更加丰富些,例如安全维护方面;

3、Spring Security 社区资源相对比 Shiro 更加丰富;

  • Spring Security 缺点:

4、Shiro 的配置和使用比较简单,Spring Security 上手复杂些;

5、Shiro 依赖性低,不需要任何框架和容器,可以独立运行.Spring Security 依赖Spring容器;

自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。 因此,一般来说,常见的安全管理技术栈的组合是这样的:

  • SSM + Shiro

  • Spring Boot/Spring Cloud + Spring Security

UserDetailsService 接口

当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中 账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

返回值 UserDetails ,这个类封装了用户”主体“。

// 表示获取登录用户所有权限 
Collection<? extends GrantedAuthority> getAuthorities(); 
// 表示获取密码 
String getPassword(); 
// 表示获取用户名 
String getUsername(); 
// 表示判断账户是否过期 
boolean isAccountNonExpired(); 
// 表示判断账户是否被锁定 
boolean isAccountNonLocked(); 
// 表示凭证{密码}是否过期 
boolean isCredentialsNonExpired(); 
// 表示当前用户是否可用 
boolean isEnabled(); 
  • UserDetailsService接口的实现类:

以后我们只需要使用 org.springframework.security.core.userdetails.Use 这个实体类即可!

PasswordEncoder 接口

  1. encode 方法
String encode(CharSequence rawPassword); 
// 表示把参数按照特定的解析规则进行解析 
  1. matches 方法
boolean matches(CharSequence rawPassword, String encodedPassword);   
// 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。
// 如果密码匹配,则返回 true;如果不匹配,则返回 false。
//第一个参数表示需要被解析的密码。第二个参数表示存储的密码。 
  1. upgradeEncoding 方法
default boolean upgradeEncoding(String encodedPassword) { return false; } 
// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回false。 
  • PasswordEncoder接口的实现类:

BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时可以使用这个解析器。
BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10。

SpringSecurity Web 权限方案

配置认证策略

  1. 引入pom依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>
  1. 设置登录系统的账号、密码
  • 方法一:在application.properties
spring.security.user.name=yinrz
spring.security.user.password=123
  • 方法二:实现UserDetailsService接口,配置 SecurityConfig
@Service
public class LoginService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
       
        // 可以实现数据库认证来完成用户登录,s参数为输入的用户名,根据用户名查到实体对象,通过getUsername()、getPassword()获取用户名和密码。
        
        String username = "yinrz";
        String password = passwordEncoder.encode("123"); // 密码进行编码
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User(username,password,auths);
    }
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("loginService")
    private UserDetailsService userDetailsService;

    // 选用BCryptPasswordEncoder密码解析器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    // 配置使用自定义的 userDetailsService , 使用自定义的用户名和密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder()); //密码进行解码
    }

}
  1. SecurityConfig 配置认证策略
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 配置认证策略
    http.formLogin()
        .loginPage("/login.html")           // 配置登录页面
        .loginProcessingUrl("/login")       // 配置登录的访问路径
        .defaultSuccessUrl("/success.html") // 配置登录成功之后跳转的 url,重定向
    //  .successForwardUrl("/success.html")    配置登录成功之后跳转的 url,转发
        .permitAll();

    http.authorizeRequests()
        .antMatchers("/","/index").permitAll()  // 符合这些路径的,不需要认证
        .anyRequest()                                       // 其他请求
        .authenticated();                                   // 需要认证

    http.csrf().disable();  // 关闭 csrf

}
  1. 前端登录页面
<form method="post" action="/login">
    用户名:<input type="text" name="username"/>
    密码:<input type="text" name="password"/>
    <input type="submit" name="提交">
</form>

action 要与 loginProcessingUrl 一致,method 必须为 post,用户名和密码的 name 必须为 username 和 password。

原因:在执行登录的时候会走一个过滤器 UsernamePasswordAuthenticationFilter :

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private boolean postOnly = true;
    ...
}

配置授权策略

  1. hasAuthority 方法

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

http.authorizeRequests()
        .antMatchers("/update").hasAuthority("admin"); // 需要用户带有admin权限
  1. hasAnyAuthority 方法

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

http.authorizeRequests()
        .antMatchers("/update").hasAnyAuthority("admin,sale"); // 需要用户带有admin权限或者sale权限
  1. hasRole 方法

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

http.authorizeRequests()
    .antMatchers("/update").hasRole("admin"); //需要用户带有admin角色

授予角色要加 “ROLE_” 前缀:

List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin");
  1. hasAnyRole 方法

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

http.authorizeRequests()
    .antMatchers("/update").hasRole("admin,sale"); //需要用户带有admin或者sale角色
  1. 自定义403没有权限页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
  1. 注解 @Secured

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

使用注解先要开启注解功能 @EnableGlobalMethodSecurity(securedEnabled=true)

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringsecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringsecurityApplication.class, args);
    }
}
@Secured({"ROLE_admin"})
@GetMapping("hello")
public String hello(){
    return "Say Hello";
}
  1. 注解 @PreAuthorize

进入方法前进行权限验证。

使用注解先要开启注解功能 @EnableGlobalMethodSecurity(prePostEnabled = true)

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SpringsecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringsecurityApplication.class, args);
    }
}
@PreAuthorize("hasAnyAuthority('admin,sale')")
@GetMapping("hello")
public String hello(){
    return "Say Hello";
}
  1. 注解 @PostAuthorize

在方法执行后再进行权限验证。

使用注解先要开启注解功能 @EnableGlobalMethodSecurity(prePostEnabled = true)

@PostAuthorize("hasAnyAuthority('admin,sale')")
@GetMapping("hello")
public String hello(){
    return "Say Hello";
}
  1. 注解 @PreFilter

进入控制器之前对数据进行过滤。

@PreFilter(value = "filterObject.id%2==0") 

10.注解 @PostFilter

权限验证之后对数据进行过滤。

@PostFilter("filterObject.username == 'admin1'") 

配置基于数据库的记住我

  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;
  1. 配置数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=root
  1. 编写配置类 SecurityConfig
@Autowired
@Qualifier("loginService")
private UserDetailsService userDetailsService;

@Autowired
private DataSource dataSource;

@Bean
public PersistentTokenRepository persistentTokenRepository(){
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); // 赋值数据源
    jdbcTokenRepository.setDataSource(dataSource);
    // jdbcTokenRepository.setCreateTableOnStartup(true);  // 自动创建表
    return jdbcTokenRepository;
}

http.rememberMe()
    .tokenRepository(persistentTokenRepository())
    .tokenValiditySeconds(60*60*24*10)                 // 设置有效时长,单位秒
    .userDetailsService(userDetailsService);
  1. 修改前端页面
记住我:<input type="checkbox" name="remember-me" title="记住密码"/>

name 必须为 remember-me 。

配置用户注销

  1. 编写配置类 SecurityConfig
http.logout()
        .logoutUrl("/logout")               // 注销的路径
        .logoutSuccessUrl("/index")         // 注销成功后的跳转的路径
        .permitAll();
  1. 修改前端页面
<a href="/logout">退出</a>
posted @ 2020-12-06 16:14  Baby丿太依赖  阅读(137)  评论(0)    收藏  举报