Spring Security是如何保护Spring的

 

%title插图%num

Spring Security是什么?

信息可能使我们最有用的价值,总有一些人会想办法来窃取我们的数据和身份信息。作为软件开发人员,我们必须去保护程序中的信息。而Sping Security正是Spring为我们提供的一个安全框架。其实Sping Security在Spring Boot没发布之前,就已经发展了很多年了,但是没有Shiro用的人多。因为在传统项目中,Shiro比较好整合,虽然没有Sping Security功能强大,但是也够用,够用嘛那就简单点好。

启用Sping Security

在pom文件中添加Sping Security依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

这样就保护了我们的程序了,当应用启动的时候,强大自动配置会检测到Sping Security出现在类路径中,因此他会初始化一些安全配置。如下

  • 所有的HTTP请求路径都需要认证
  • 不需要特定的角色和权限
  • 没有登录页面
  • 认证过程是通过HTTP basic认证对话框实现的
  • 系统只有一个用户,用户名为user

但是我们大多数应用的安全需求和这些基础的安全特性不一样,我们需要登录,注册,角色,鉴权……等等,那我们就需要编写一些显示的配置,覆盖掉自动配置为我们提供的功能。

以前都是冗长的XML配置等,近几年的几个版本,Sping Security都支持基于Java的配置,这种方式更加容易开发和阅读(毕竟写代码谁都会,我们要的是写出人能看懂的代码)。

我们可以通过覆盖WebSecurityConfigurerAdapter基础配置类中定义的configure()方法来进行配置。

package cn.stylefeng.guns;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .jdbcAuthentication()
            .dataSource(dataSource).usersByUsernameQuery("select username,password from users where username = ?")
                .authoritiesByUsernameQuery("select username,authority from userAuthorities where username = ?");
    }
}

这么做了之后,用户的密码会以明文方式存储在数据库中,一般数据库中密码会进行转码处理,那这样的sql就会查询失败,因为密码与账号不匹配。

所以我们改造代码。

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .jdbcAuthentication()
        .dataSource(dataSource).usersByUsernameQuery("select username,password from users where username = ?")
            .authoritiesByUsernameQuery("select username,authority from userAuthorities where username = ?")
            .passwordEncoder(new StandardPasswordEncoder("ds65ws"));
}

passwordEncoder对密码进行加密处理,这个方法也接受Sping Security中PasswordEncoder接口的任意实现。Sping Security的加密模块包括了多个这样的实现。

  • BCryptPasswordEncoder:使用bcrypt强哈希加密
  • NoOpPasswordEncoder:不进行任何转码
  • Pbkdf2PasswordEncoder:使用PBKDF2加密
  • SCryptPasswordEncoder:使用scrypt哈希加密
  • StandardPasswordEncoder:使用SHA-256哈希加密

甚至你可以自定义的实现。PasswordEncoder接口很简单。

public interface PasswordEncoder{
    String encode(CharSequence rawPassword);
    boolean matches(CharSequence rawPassword,String encodedPassword);
}

不管用什么加密,数据库中的密码是永远不会解码的。

怎么做web防御

在SecurityConfig 类中添加如下方法:

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            .authorizeRequests() //对Requests鉴权拦截
            .antMatchers("/index","/order")  //对/index、/order路径请求进行鉴权
            .hasRole("admin")  //需要admin角色才能访问
            .antMatchers("/","/**")   //放开其他页面
            .permitAll();  //所有人都可以访问
}

一般我们是拦截所有页面,放开登录、注册、首页等不需要登录的页面出去。

对authorizeRequests的调用会返回一个对象,基于他我们可以对指定的url的安全需求。

声明在前面的规则会比后面的规则优先级高,例子中对/index和/order的拦截会高于放过所有请求。

保护路径的配置方法
方法 做什么的
access(String) 如果给定的SpEL表达式计算结果为true,就允许访问
anonymous() 允许匿名访问
authenticated() 允许认证过的用户访问
denyAll() 无条件拒绝所有访问
fullAuthenticated() 如果用户是完整认证的(不是通过Remember-me功能认证的),就允许访问
hasAnyAuthorith(String…) 如果用户具备给定权限中的某一个,就允许访问
hasAnRole(String…) 如果用户具备给定角色中的某一个,就允许访问
hasAuthority(String) 如果用户具备给定去权限,就允许访问
hasIpAddress(String) 如果请求来自给定IP地址,就允许访问
hasRole(String) 如果用户具备给定角色,就允许访问
not() 对其他访问方法的结果求反
permitAll() 无条件允许访问
rememberMe() 如果用户是通过Remember-me功能认证的,就允许访问

创建自定义登录页面

我们调用formLogin()配置自定义的登录表单。当Sping Security断定用户没有认证并且需要登录的时候,它就会将用户重定向到该路径。

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            .authorizeRequests()
            .antMatchers("/index","/order")
            .hasRole("admin")
            .antMatchers("/","/**")
            .permitAll()
            .and()
            .formLogin()
            .loginPage("/login");
}

他还可以通过defaultSuccessUrl方法指定用户登录后调至的默认页面,以及是否强制调至默认页面。

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers("/index","/order")
                .hasRole("admin")
                .antMatchers("/","/**")
                .permitAll()
                .and()
                .formLogin()
                .loginPage("/login")
//                .defaultSuccessUrl("/index",true) //之前是不是登录页面都强制调至默认页面
                .defaultSuccessUrl("/index");  //如果之前是登录页面,跳转默认页面
    }

防止跨站请求伪造

CSRF是一种常见的安全攻击,Sping Security内置了CSRF保护。他默认是开启的,我们不需要配置他。我们需要做的就是每个表单中都要有一个名为“_csrf”的字段,他会持有CSRF token。

如果你用的Spring MVC的JSP标签库或者Sping Security的Thymeleaf方言,他会自动生成这个标签。

得到用户信息

在控制层中,我们只需要加入@AuthenticationPrincipal注解,我们就可以获取到用户信息。

@Controller
public class domeController {

    @RequestMapping("/index")
    public String index(String name, @AuthenticationPrincipal User user){
        return null;
    }
}

标签

发表评论