SpringSecurity介绍

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

应用级别的安全主要分为“验证( authentication) ”和“(授权) authorization ”两个部分。这也是Spring Security主要需要处理的两个部分。“ Authentication ”指的是建立规则( principal )的过程。规则可以是一个用户、设备、或者其他可以在我们的应用中执行某种操作的其他系统。” Authorization “指的是判断某个 principal 在我们的应用是否允许执行某个操作。在 进行授权判断之前,要求其所要使用到的规则必须在验证过程中已经建立好了。

除了验证机制, Spring Security 也提供了一系列的授权能力。主要感兴趣的是以下三个方面:

  1. 对web请求进行授权
  2. 授权某个方法是否可以被调用
  3. 授权访问单个领域对象实例

关于SpringSecurity的学习主要是参考http://www.tianshouzhi.com/api/tutorials/spring_security_4/250,这里面还提供了对源码的解读,对于我这种渣渣来说,看起来还是有点费劲的

在Springsecurity中有两种配置方式,是xml文件配置和java配置,两者配置差别不打,个人感觉java配置使用起来比较方便,所以主要记录一下java配置的过程。

java基本配置

新建项目

新建一个SpringBoot项目,pom.xml中需要的包(勾选或者直接手写)

devtools
thymeleaf
security

在pom中添加这三个包之后,可以启动项目看看,访问项目下的任意一个路径都会被拦截要求输入用户名和密码,由于啥都还没有设置,所以用户名是user,密码在启动springboot的时候在console输出了,类似f1ea0b2d-b053-40fd-abc5-065a4f45b266这样的一串就是默认密码。

java 白名单检验 spring security 白名单规则_java

java 白名单检验 spring security 白名单规则_spring security_02

配置自己的用户名密码

在项目根目录下添加SecurityConfig.java文件

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}

然后访问任意一个URL都会跳转到下面这个界面

java 白名单检验 spring security 白名单规则_spring security_03

输入刚刚设置的用户名密码就可以重新跳转回所要访问的界面

这段代码内容很少,但事实上已经做了很多的默认安全验证,包括:

  1. 访问应用中的每个URL都需要进行验证
  2. 生成一个登陆表单
  3. 允许用户使用username和password来登陆
  4. 允许用户注销
  5. CSRF攻击拦截
  6. Session Fixation攻击
  7. 安全Header集成

配置自定义的登录界面

SecurityConfig.java中添加下面的代码,主要是说拦截所有请求,然后配置自定义的登陆界面的路径为“/login”

@Override
protected void configure(HttpSecurity http) throws Exception {
       http
            .authorizeRequests()
                  .anyRequest().authenticated()
                  .and()
            .formLogin()
                  .loginPage( "/login")
                  .permitAll();      
}

在templates文件夹下添加login.html文件,我这个html文件是从之前写的直接拷贝过来的。

<html xmlns:th="http://www.thymeleaf.org">
<head>

<meta content="text/html;charset=utf-8" />
<title>登录界面</title>
<style type="text/css">

.starter-template {
    padding: 40px 15px;
    text-align: center;
}
</style>
</head>

<body>


    <div class="container">
        <div class="starter-template">
            <p th:if="${param.logout}" class="bg-warning">已成功注销</p>
            <p th:if="${param.error}" class="bg-danger">有错误,请重试</p>
            <h2>使用账号密码登陆</h2>
            <form name="form" th:action="@{/login}"  method="POST">
                <div class="form-group">
                    <label for="username">账号:</label> 
                    <input type="text" class="form-control" name="username" value="" placeholder="账号" />
                </div>
                <div class="form-group">
                    <label for="password">密码:</label> 
                    <input type="password" class="form-control" name="password" placeholder="密码" />
                </div>
                <input type="submit" id="login" class="btn btn-primary" />
            </form>
        </div>
    </div>
</body>
</html>

然后在添加一个文件WebMvcConfig,将“/login”映射到“login.html”

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");

    }

}

这个时候重新访问刚刚的路径,就会拦截到自定义的登录界面

java 白名单检验 spring security 白名单规则_java 白名单检验_04

这里Html文件中的用户名密码,默认的name属性名字是“username”和“password”,否则springsecurity是获取不到值的,如需要自定的话,可以这么设置

@Override
protected void configure(HttpSecurity http) throws Exception {
       http
            .authorizeRequests()
                  .anyRequest().authenticated()
                  .and()
             .formLogin()
                  .loginPage( "/login")
                  .usernameParameter("username")
                  .passwordParameter("password")
                  .permitAll();
}

退出登录

可以在configure函数下继续设置:

@Override
protected void configure(HttpSecurity http) throws Exception {
       http
            .authorizeRequests()
                  .anyRequest().authenticated()
                  .and()
             .formLogin()
                  .loginPage( "/login")
                  .usernameParameter("username")
                  .passwordParameter("password")
                  .permitAll()
                  .and()
             .logout()
                 .logoutUrl("/logout")                                                 
                 .logoutSuccessUrl("/index")                                           
                 .invalidateHttpSession(true) 
                 .permitAll();
}

可以直接从代码中看出来,就是访问“/logout”的时候会退出登录,若成功退出便会访问到“/index”这个路径,这个比较简单,就不贴代码出来了,可以下载我的源码去看看。

拦截url和授权

之前设置的都是拦截全部的URL,不管什么URL都拦截到登录界面,也可以自定义拦截,同样是在configure函数下添加设置:

  • resources路径下的文件和以show开头的路径无需登录结课访问
  • test该URl只能由用户为ADMIN的访问
  • index该路径可由ADMIN和USER_ADMIN的用户访问
@Override
public  void configure(HttpSecurity http) throws Exception {
       http
        .authorizeRequests()                                                              
        .antMatchers( "/resources/**", "/show*").permitAll()  
        .antMatchers( "/test").hasRole("ADMIN" )                      
        .antMatchers( "/idnex").access("hasRole('ADMIN') and hasRole('USER_ADMIN')")  
        .antMatchers( "/AddUser").access("hasRole('ADMIN') and hasRole('USER')")  
//          .regexMatchers("").permitAll()正则表达式匹配
        .anyRequest().authenticated()       

//             .authorizeRequests()
//                   .anyRequest().authenticated()
                  .and()
            .formLogin()
                  .loginPage( "/login")
                  .usernameParameter("username")
                  .passwordParameter("password")
                  .permitAll()
                  .and()
            .logout()
             .logoutUrl("/logout")                                                 
             .logoutSuccessUrl("/index")                                           
             .invalidateHttpSession(true) 
             .permitAll();      
}

关于用户的权限,可在设置用户名和密码是指定

@Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("1").password("1").roles("ADMIN").and()
                .withUser("2").password("2").roles("USER").and()
                .withUser("3").password("3").roles("USER_ADMIN").and()
                .withUser("4").password("4").roles("USER");

    }

配置mysql数据库中的用户名密码

我数据库中一张表SysUser(id,username,password,role)

在java中新建一个entity类SysUser.java注意这个类需要实现UserDetails接口。数据库中的role字段需要有”ROLE_”作为前缀,或者需要在构建的时候拼接“ROLE_”,我这里是拼接的。

@Entity
public class SysUser implements UserDetails{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue
    private Long id;
    private String username;
    private String password;
    private String role;

    //getter....setter....

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
        auths.add(new SimpleGrantedAuthority("ROLE_"+role));
        return auths;
    }

    @Override
    public boolean isAccountNonExpired() {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public boolean isEnabled() {
        // TODO Auto-generated method stub
        return true;
    }



}

新建一个JPA的类SysUserRepository.java这个很简单就不做解释了。

import org.springframework.data.jpa.repository.JpaRepository;

public interface SysUserRepository extends JpaRepository<SysUser, Long>{
    SysUser findByUsername(String username);
}

新建一个CustomUserService.java文件

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class CustomUserService implements UserDetailsService {

    @Autowired
    SysUserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        SysUser user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return user;
    }

}

最后在SecurityConfig.java文件中添加

@Bean
    UserDetailsService customUserService(){
        return new CustomUserService();
    }

在configure方法下添加

auth.userDetailsService(customUserService());

这样就可以将数据库中的用户名密码添加到springsecurity中。
关于原理可以去参考一下http://www.tianshouzhi.com/api/tutorials/spring_security_4/250

测试这里便不做了,可以下载我的源码去试试看