Spring Security

概要

Spring Security基于Spring框架,提供了一套Web应用安全性的完整解决方案。Spring Boot对于Spring Security提供了自动化配置方案,可以使用更少的配置来使用Spring Security.

一般来说,web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)。

用户认证:验证某用户是否是系统中的合法主体。即用户能否登录该系统。

用户授权:验证某用户是否有权限执行某个操作。

同类产品:

  • shiro:Apache旗下的轻量级权限控制框架

Spring Security和Shiro比较

Spring Security特点:

  • 重量级框架(依赖很多组件)
  • 和Spring无缝整合
  • 全面的权限控制
  • 专门为web开发设计(旧版本不能脱离Web环境使用,新版本对整个框架进行了分层抽取,分成了核心模块和web模块。单独引入核心模块就可以脱离web环境)

Shiro特点:

  • 轻量级
  • 通用性
    • 好处:不局限于web环境,可以脱离web环境使用
    • 坏处:在web环境下一些特定的需求需要手动编写代码

常见安全管理技术栈组合:

  • SSM+Shiro
  • Spring Boot/Spring Cloud + Spring Security

Spring Security模块划分:

  1. 核心 spring-security-core.jar

包含身份验证和访问控制类和接口,远程支持和基本配置API. 使用Spring-security的任何应用程序都需要此模块. 支持独立的应用程序,远程客户端,方法(服务层)安全性和JDBC用户配置. 包含包:

org.springframework.security.core
org.springframework.security.access
org.springframework.security.authentication
org.springframework.security.provisioning

  1. 远程处理 spring-security-remoting.jar

提供与Spring Remoting的集成. 除非你非要编写使用Spring Remoting的远程客户端,否则不需要这样做

主要包:

org.springframework.security.remoting

  1. 网页 spring-security-web.jar

包含过滤器和相关的Web安全基础结构代码.任何与Servlet API依赖的东西. 如果你需要Spring Security Web认证服务和基于URL的访问控制,则需要它.

主要包:

org.springframework.security.web

  1. 配置 spring-security-config.jar

包含安全名称空间解析代码和Java配置代码.如果你使用Spring Security XML名称空间精选配置或Spring Security 的java配置支持,则需要它.

主要包是:

org.springframework.security.config

快速入门

1:创建Spring Boot项目

2:导入Spring Security依赖

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

3:编写controller类测试

在这里插入图片描述

注:Spring Security有默认账号密码,账号:user,密码输出在控制台中。

在这里插入图片描述
在这里插入图片描述

Spring Security基本原理

Spring Security本质是一个过滤器链(有很多过滤器)

几个重要的过滤器

  1. FilterSecurityInterceptor(是一个方法级的权限过滤器,基本位于过滤器的最底部)

    在这里插入图片描述

super.beforeInvocation(filterInvocation)  // 查看之前的filter是否通过
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());          // 真正调用后台服务
  1. ExceptionTranslationFilter(异常过滤器,用来处理在认证授权中抛出的异常)

在这里插入图片描述

  1. UsernamePasswordAuthenticationFilter(对/login的post请求做拦截,校验表单中的用户名、密码)

在这里插入图片描述

两个重要的接口

1:UserDetailsService

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

如果需要自定义逻辑时, 只需要实现UserDetailsService接口。去数据库查询用户名和密码的过程就写在这里面。(UsernamePasswordAuthenticationFilter中是获取用户名和密码,但查询是在UserDetailsService中做的)

在这里插入图片描述

自定义用户认证:

  1. 需要继承UsernamePasswordAuthenticationFilter类
  2. 重写attemptAuthentication方法(用户如何认证)
  3. 重写successfulAuthentication方法(用户认证通过的操作)
  4. 重写unsuccessfulAuthentication方法(用户认证失败的操作)
  5. 创建类实现UserDetailsService接口
  6. 编写查询数据过程,返回User对象(User是安全框架提供的对象)

2:PasswordEncoder

数据加密接口,用于返回User对象里面的密码加密。

在这里插入图片描述

使用示例:

        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        // 对密码进行加密
        String password = bCryptPasswordEncoder.encode("password");
        // 打印加密之后的数据
        System.out.println(password);
        // 判断原字符加密之后是否和加密之前匹配
        boolean result = bCryptPasswordEncoder.matches("password", password);

Web权限方案

用户认证

设置登录的用户名和密码

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

// application.yml中配置用户密码 启动项目之后控制台不会再输出之前的默认密码(因为已经指定了用户名和密码)
spring:
  security:
    user:
      name: yangsha
      password: yangsha

在这里插入图片描述

第二种方式:基于配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("yangsha1").password(password).roles("admin"); // 将该用户存入内存中
    }
}

在这里插入图片描述

按照上述代码启动项目登录后会报错,原因是没有注册PasswordEncoder组件。

改进之后的代码:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("yangsha1").password(password).roles("admin"); // 将该用户存入内存中
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

第三种:自定义实现类 (大多数情况都使用该方式)

第一步:创建配置类,指定使用的UserDetailService实现类

第二步:编写实现类,返回User对象。(User对象有用户名、密码和操作权限)

代码示例:

/**
 * SpringSecurity配置类:用于设置登录的用户名和密码
 * @author : yangsha
 * @date : 2021/4/28
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
/**
 *  获得User对象,并进行返回
 * @author : yangsha
 * @date : 2021/4/28
 */
@Service
public class MyUserDetailService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 权限
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        // 返回User对象 权限不能传null
        return new User("yangsha", new BCryptPasswordEncoder().encode("123"), auths);
    }
}

基于数据库的认证

/**
 * SpringSecurity配置类:用于设置登录的用户名和密码
 * @author : yangsha
 * @date : 2021/4/28
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    /**
     * 配置用户名 密码
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
    }

    /**
     * 放开h2数据库的权限
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                //.antMatchers("/online-signs/**")
                .antMatchers("/h2-console/**");
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
/**
 *  获得User对象,并进行返回
 * @author : yangsha
 * @date : 2021/4/28
 */
@Service
public class MyUserDetailService implements UserDetailsService {

    @Autowired
    SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 从数据库取出当前用户
        SysUser sysUser = sysUserService.getOne(new QueryWrapper<SysUser>().eq("user_name", s));
        // 判断
        if(ObjectUtils.isEmpty(sysUser)){
            throw new RuntimeException("用户名不存在");
        }
        // 权限
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        // 返回User对象 权限不能传null
        return new User(sysUser.getUserName(), new BCryptPasswordEncoder().encode(sysUser.getPassword()), auths);
    }
}

自定义登录页面

  1. Security中重写(configure(HttpSecurity http)方法)
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin() // 自定义编写的登录页面
        .loginPage("/login.html")   // 登录页面设置
        .loginProcessingUrl("/user/login")  // 登录的controller路径
        .defaultSuccessUrl("/test/index").permitAll()  // 登录成功之后的跳转路径
        .and().authorizeRequests()
            .antMatchers("/", "/user/test", "/user/login").permitAll()  //这些路径无需认证
        .anyRequest().authenticated()    // 所有路径都要认证
        .and().csrf().disable();    // 关闭csrf防护
}
  1. 编写login.html

在这里插入图片描述

注:表单中用户名和密码必须定义为username、password。原因如下图:

在这里插入图片描述

如果想要改变表单中的传参,需要重写该方法。

  1. 测试

访问user/test不需要认证

在这里插入图片描述

访问user/index需要认证
在这里插入图片描述

用户授权

基于权限访问控制

hasAuthority

如果当前主体具有指定权限,就返回true,否则返回false。(一般只针对一个角色操作)

在这里插入图片描述
在这里插入图片描述

hasAnyAuthority

如果当前主体具有多个权限中的某一个权限(角色中间用逗号分隔开),就返回true。(针对多个角色的操作)

在这里插入图片描述

基于角色进行权限控制

hasRole

如果当前主体具有指定角色,就返回true。(一般只针对一个角色操作)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

hasAnyRole

如果当前主体具有多个角色中的某一个角色,就返回true。(针对多个角色的操作)

自定义403(没有权限)界面

当上述两种权限不够的时候,就会返回403.

在这里插入图片描述

自定义配置:

在这里插入图片描述

基于注解的认证授权

@Secure:用户具有某个角色,才能访问该方法

  1. 需要先在启动类(或者配置类)开启权限注解。
@EnableGlobalMethodSecurity(securedEnabled = true)

在这里插入图片描述
在这里插入图片描述

@PreAuthorize:方法前拦截判断是否有权限

需要先在启动类(或者配置类)开启权限注解。

@EnableGlobalMethodSecurity(prePostEnabled = true)

在这里插入图片描述

@PostAuthorize:方法执行后再判断是否有权限(适合验证带有返回值的权限)

一般此注解用的比较少

需要先在启动类(或者配置类)开启权限注解。

@EnableGlobalMethodSecurity(prePostEnabled = true)

在这里插入图片描述

测试结果:如果用户没有权限,会先输出“hello,index”,但不会返回hello。而会返回403(没有权限)

注销

在配置类中添加退出映射地址

// 配置注销
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();

自动登录

自动登录流程:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

csrf

csrf:跨域请求伪造,一种恶意的攻击方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值