Spring学习笔记-C9-SpringSecurity

Chapter09 Spring Security

9.1 Spring Security简介

Spring Security 是为基于Spring的应用程序提供声明式安全保护的安全性框架。SpringSecurity充分利用了DI和AOP技术。本章将使用SpringSecurity保证Web安全性。

9.1.1 SpringSecurity模块

SpringSecurity被分成了11个模块

ACLAccessControlList访问控制列表为域对象提供安全性
切面当使用SpringSecurity注解时,会使用基于AspectJ的且米娜,而不是使用标准的SpringAOP
CAS客户端提供与Jasig的中心认证服务(Central Authentication Service)进行集成的功能
配置包含通过XML和Java配置SpringSecurity的功能支持
核心提供SpringSecurity基本库
加密提供了加密和密码编码的功能
LDAP支持基于LDAP进行认证
OpenID支持使用OpenID进行集中式认证
Remoting提供了对SpringRemoting的支持
标签库SpringSecurity的JSP标签库
Web提供了SpringSecurity基于Filter的Web安全性支持

其中Core和Configuration模块必须包含在应用程序的类路径下。一般为了保护web应用我们还需要Web模块。

9.1.2 过滤Web请求

SpringSecurity借助一系列ServletFilter来提供各种安全性功能。
DelegatingFilterProxy是一个特殊的ServletFilter。它将工作委托给javax.servlet.Filter实现类,这个实现类作为一个注册在Spring应用上下文中。

在web.xml中配置过滤器(方法1)

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
</web-app>

在Java中配置(方法2)


/**
 * SpringSecurity配置类
 * @author NikoBelic
 * @create 11/01/2017 14:00
 */
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer
{

}

不管我们使用web.xml配置还是AbstractSecurityWebApplicationInitializer得子类来配置DelegatingFilterProxy,它都会拦截发往应用中的请求,并将请求委托给ID为springSecurityFilterChain的bean。

配置Security安全细节

/**
 * 配置Web安全的细节
 * @author NikoBelic
 * @create 11/01/2017 15:32
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
    }
}

这个简单的默认配置指定了该如何保护HTTP请求,以及客户端认证用户的方案。通过调用authorizeRequest()和anyRequest().authenticated()就会要求所有进入应用的HTTP请求都要进行认证。他也配置SpringSecurity支持基于表单的登陆以及HTTPBasic方式的认证。
由于没有配置configure(AuthenticationManagerBuilder)方法,所以没有用户存储支撑认证过程。没有人能够登陆成功。
这里写图片描述
TODO:

  • 配置用户存储
  • 指定哪些请求需要认证,哪些请求不需要认证,以及所需要的权限
  • 提供一个自定义的登陆界面,替代原来简单的默认登录页。
  • 基于安全限制,有选择的在Web视图上显示特定的内容。

9.2 选择查询用户详细信息的服务

SpringSecurity能够基于各种数据存储来认证用户。它内置了多种常见的用户存储场景,如内存、关系型数据库以及LDAP。

9.2.1 使用基于内存的用户存储


    /**
     * 基于内存的用户登陆权限配置
     * @Author NikoBelic
     * @Date 11/01/2017 17:43
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("lxw").password("lxw").roles("USER").and()
                .withUser("admin").password("admin").roles("USER","ADMIN");
    }

9.2.2 基于数据库表进行认证

SpringSecurity会内置一些查询语句,当查找用户信息时会自动执行。
当然我们也可以自定义查询语句来获取用户信息。如下是相关java配置。
将默认的sql查询替换为自定义的设计时,要遵循查询的基本协议。

  • 所有查询都将用户名作为唯一的参数
  • 认证查询会选取用户名、密码以及启用状态信息。
  • 权限查询会选取0行或多行包含该用户名以及其权限信息的数据。
  • 群组权限查询会选取0行或多行数据,每行数据中都会包括群组ID、群组名称以及权限。
/**
 * 配置Web安全的细节
 * @author NikoBelic
 * @create 11/01/2017 15:32
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    DataSource dataSource;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
    }

    /**
     * 用户登陆权限配置
     * @Author NikoBelic
     * @Date 11/01/2017 17:43
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 基于内存的用户权限配置
        //auth.inMemoryAuthentication().withUser("lxw").password("lxw").roles("USER").and()
        //        .withUser("admin").password("admin").roles("USER","ADMIN");

        // 基于JDBC的用户权限配置

        auth.jdbcAuthentication().dataSource(dataSource).
                usersByUsernameQuery("select username,password,true from tb_user where username = ?").
                authoritiesByUsernameQuery("select username,'ROLE_USER' from tb_user where username = ?");
    }
}

其中datasource是通过自动装配注入的,因此我们需要在root中或者xml中先配置一下数据源。

/**
 * Spring上下文配置类
 * 由ContextLoaderListener加载的bean
 * @author NikoBelic
 * @create 09/01/2017 20:30
 */
@Configuration
@ComponentScan(basePackages = "chapter07",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)})
public class RootConfig
{

    @Bean
    public DataSource dataSource()
    {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/dmes?characterEncoding=UTF-8");
        ds.setUsername("root");
        ds.setPassword("*******");
        return ds;
    }
}

之后再访问web层就可以使用数据库中已经存储的用户去登陆SpringSecurity校验界面了。
补充:一般情况下数据库中的密码并不是明文存储的,因此我们需要一个密码转换方法将SpringSecurity从DB中获取到的密码进行解码。
举个栗子:DB中username=niko,password=niko_salt(这是密文哦,用户注册后系统加工后的处理结果)。
那么通过MyPasswordDecoder类处理后,我们通过SpringSecurity登录验证的输入为:niko,niko就能正常访问了。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
    }

    /**
     * 用户登陆权限配置
     *
     * @Author NikoBelic
     * @Date 11/01/2017 17:43
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 基于内存的用户权限配置
        //auth.inMemoryAuthentication().withUser("lxw").password("lxw").roles("USER").and()
        //        .withUser("admin").password("admin").roles("USER","ADMIN");

        // 基于JDBC的用户权限配置

        auth.jdbcAuthentication().dataSource(dataSource).
                usersByUsernameQuery("select username,password,true from tb_user where username = ?").
                authoritiesByUsernameQuery("select username,'ROLE_USER' from tb_user where username = ?").
                passwordEncoder(new MyPasswordDecoder("_salt"));
    }
}

/**
 * 密码解码器
 * @Author NikoBelic
 * @Date 11/01/2017 19:26
 */
class MyPasswordDecoder implements PasswordEncoder
{
    private String salt;

    public MyPasswordDecoder(String salt)
    {
        this.salt = salt;
    }

    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString() + salt;
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodePassword)
    {
        return (rawPassword + salt).equals(encodePassword);
    }
}

9.2.3 自定义查询用户服务

/**
 * 自定义用户权限查询类
 * 可以配置为MongoDB、Redis等等
 * @Author NikoBelic
 * @Date 11/01/2017 19:38
 */
class MyUserService implements UserDetailsService
{
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        if (username.equals("test"))
        {
            List<GrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("ROLE_SPITTER"));
            return new User("test", "test",authorities );
        }
        throw new UsernameNotFoundException("没有查询到该用户");
    }
}

9.3 拦截请求

针对不同的功能进行不同等级的安全认证是项目中经常会用到的。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 对全部web请求进行安全校验
        //http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();

        // 按照URL路径设置安全校验
        http.authorizeRequests().antMatchers("/weibo/profile").authenticated()
                .antMatchers(HttpMethod.GET,"/weibo/testSecurity").authenticated()
                .anyRequest().permitAll();

    }

以上配置指定了部分url禁止访问,匹配url的字符串支持Ant风格(antMatchers)的通配符。也可以在一个matcher中指定多个url链接,用逗号隔开。url也支持正则表达式regexMachers和anyRequest。
.regexMachers("spitters/.*").authenticated()/spitters/**所拦截的url是一样的。
用来定义如何保护路径的配制方法

access(String)如果给定的SpEL表达式计算结果为true,就允许访问。
anonymous()允许匿名用户访问
authenticated()允许认证过的用户访问
denyAll()无条件拒绝所有访问
fullyAuthenticated()如果用户是完整认证的话(不是通过Remember-me功能认证的)就允许访问
hasAnyAuthority(String…)如果用户具备给定全险种的某一个的话,就允许访问
hasAnyRole(String…)如果用户具备给定角色中的某一个的话,就允许访问
hasAuthority(String)如果用户具备给定权限的话,就允许访问
hasIpAddress(String)如果请求来自给定IP地址的话,就允许访问。
hasRole(String)如果用户具备给定角色的话,就允许访问
not()对其他访问方法的结果求反
permitAll()无条件允许访问
rememberMe()如果哟过户是通过Remember-me功能认证的,就允许访问

我们可以将任意数量的antMachers、regexMatcgers、anyRequest连接起来,以满足web安应用安全规则的需要。这些规则按照给定的顺序发挥作用,所以必须将最具体(细粒度)的请求路径放在前面,而最不具体的路径(anyRequest)放在最后面。否则后面的路径配置将会被前面的覆盖。

9.3.1 使用Spring表达式进行安全保护

使用SpEL表达式可以使安全限制更加灵活强大,以上表格展示了所有限制方法,但是假如我们需要使用一些特殊的安全机制,那么可以使用SpEL表达式。

authentication用户的认证对象
denyAll结果始终为false
hasAnyRole(list of roles)如果用户被授予了列表中的人已制定角色,结果为true
hasRole(role)如果用户被授予了指定的角色,结果为true
hasIpAddress(IP)如果来自指定的IP,结果为true
isAnonymous()如果当前用户为匿名用户,结果为true
isAuthenticated()如果当前用户进行了认证,结果为true
isFullyuAuthenticated()如果当前用户进行了完整认证,结果为true
isRememberMe()如果当前用户是通过Remember-me自动认证的,结果为true
permitAll结果为true
principal用户的principal对象

.antMachers(“/spitter/me”).access(“hasRole(‘ROLE_SPITTER’) and hasIpAddress(‘192.168.1.2’)”)

9.3.2 强制通道的安全性

将访问路径强制转换为https

http.authorizeRequests().antMatchers("/weibo/profile/*").hasRole("USER")
                .antMatchers(HttpMethod.GET,"/weibo/testSecurity").authenticated()
                .and()
                .requiresChannel()
                .anyRequest().requiresSecure();

9.3.3 防止跨站请求伪造

SpringSecurity会默认启动CSRF防护,因此我们无法向其他站点提交请求,以防止页面在被篡改的情况下站点被CSRF攻击。我们可以在jsp页面中的form表单中添加csrf隐藏域(后台传过来的)。

<input type="hidden" name = "${__csrf。paramteterName}" value="${__csrf.token}"/>

或者在SpringSeccurity中禁用CSRF防护。

http
    ...
    .csrf().disable()

9.4 认证用户

上面的安全配制方法,虽然配置了一些用户权限问题,但是没有设置登陆界面,所以你咋让我认证啊?
解决方案如下

// 按照URL路径设置安全校验
        http.formLogin() // 启动默认的登陆表单
                .and()
                .authorizeRequests()// 授权通过以后
                .antMatchers("/weibo/profile/*").hasRole("USER") // 访问指定路径必须拥有USER角色权限
                .antMatchers(HttpMethod.GET,"/weibo/testSecurity").authenticated() // 指定url只允许GET请求
                .anyRequest().permitAll(); //其他请求均可通过

9.4.1 添加自定义的登陆页

自己造一个

form['weibo/login' POST](input(username),input(password),submit())

注意表单提交位置、输入域名称就可以了,简单的一匹。

http.csrf().disable().formLogin()
                .loginPage("/weibo/showLogin")// 登陆表单
                .and()
                .authorizeRequests()// 授权通过以后
                .antMatchers("/weibo/profile/*").hasRole("USER") // 访问指定路径必须拥有USER角色权限
                .antMatchers(HttpMethod.GET,"/weibo/testSecurity").authenticated() // 指定url只允许GET请求
                .anyRequest().permitAll(); //其他请求均可通过

Controller

/**
     * 跳转登陆页面
     * @Author NikoBelic
     * @Date 12/01/2017 19:46
     */
    @RequestMapping(value = "/showLogin",method = RequestMethod.GET)
    public String showLogin()
    {
        return "/weibo/login";
    }

9.4.2 启用HTTP Basic认证

对于普通用户来说,展现一个login表单还可以,但是对于应用程序来说就需要另外一种验证方式了。
启用HTTP Basic认证的方式很简单。

// 按照URL路径设置安全校验
        http.csrf().disable().formLogin()
                .loginPage("/weibo/showLogin")// 登陆表单
                .and()
                .httpBasic()
                .and()
                .authorizeRequests()// 授权通过以后
                .antMatchers("/weibo/profile/*").hasRole("USER") // 访问指定路径必须拥有USER角色权限
                .antMatchers(HttpMethod.GET,"/weibo/testSecurity").authenticated() // 指定url只允许GET请求
                .anyRequest().permitAll(); //其他请求均可通过

模拟Basic认证请求
这里写图片描述

9.4.3 RememberMe功能和注销功能

http.csrf().disable().formLogin()
                .loginPage("/weibo/showLogin")// 登陆表单
                .and()
                .logout() // 注销  清楚remember-me的token
                .logoutSuccessUrl("/weibo/register")
                .and()
                .httpBasic()//启动服务器认证功能
                .and()
                .rememberMe() // 启动记住我功能
                .tokenValiditySeconds(10) // 只保存10s的token
                .key("weiboKey")
                .and()
                .authorizeRequests()// 授权通过以后
                .antMatchers("/weibo/profile/*").hasRole("USER") // 访问指定路径必须拥有USER角色权限
                .antMatchers(HttpMethod.GET,"/weibo/testSecurity").authenticated() // 指定url只允许GET请求
                .anyRequest().permitAll(); //其他请求均可通过

9.5 小结

Spring Security提供了一种简单、灵活且强大的机制来保护Web应用程序。借助一系列Servlet Filter能够控制对Web资源的访问,包括MVC控制器。借助于Java配置模型,能够简洁的声明Web安全性功能。
当认证用户是,可以使用基于内存用户库、关系型数据库、LDAP目录服务器来配置认证功能。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值