Spring security详解——学习笔记

Spring Security

1、SpringSecurity 框架简介

1.1、概要

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

1.2、SpringSecurity特点
  • 和 Spring 无缝整合。
  • 全面的权限控制。
  • 专门为 Web 开发而设计。
    • 旧版本不能脱离 Web 环境使用。
    • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独 引入核心模块就可以脱离 Web 环境。
1.2、Shiro

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

特点:
  • 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求 的互联网应用有更好表现。 ⚫
  • 通用性。
    • 好处:不局限于 Web 环境,可以脱离 Web 环境使用。
    • 缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有 Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。 自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方 案,可以使用更少的配置来使用 Spring Security。

2、认证和授权等基本概念

2.1 认证(authentication)
2.1.1 系统为什么要认证?

认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问系统的资源

2.1.2 什么是认证?

用户认证就是判断用于身份是否合法的过程

2.2 会话(session)

​ 用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

2.2.1基于session的认证方式

​ 它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id存放到cookie中,这样用户客户端请求时带上session_id就可以验证服务器端是否存在 session数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。

2.2.2基于token的认证方式

​ 它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到cookie或localStorage(浏览器的本地存储),等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。可以使用Redis存储用户信息(分布式中共享session)。
基于session的认证方式由Servlet,规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie;基于 token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。

2.3 授权(authorization)
2.3.1 为什么要授权(控制资源被访问)?

因为不同的用户可以访问用户的资源是不同的。

2.3.2 什么是授权?

授权: 授权是用户认证通过后,根据用户的权限来控制用户访问资源的过程。

拥有资源的访问权限则正常访问,没有权限则拒绝访问。

3、java的安全框架实现

主要有三种方式
  • shiro:轻量级的安全框架,提供认证授权,会话管理,密码管理、缓存管理等功能。

  • Spring Security:功能比 Shiro强大,更复杂,权限控制细粒度更高,对OAuth2支持更好,与Spring框架无绛集合,使Soring Boot集成很快捷。

  • 自己搭建:基于过滤器(filter)和AOP 来实现,难度大,没必要。

4、Spring Security

4.1 什么是Spring Security?

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

4.2、Spring Security内置账户的实现
  • 设置账户时,每个账户都必须有一个角色
  • 密码必须加密,通过BCryptPasswordEncoder进行加密
4.3、SpringSecurity 基本原理
  • SpringSecurity 本质是一个过滤器链:

从启动是可以获取到过滤器链:

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter org.springframework.security.web.context.SecurityContextPersistenceFilter org.springframework.security.web.header.HeaderWriterFilter org.springframework.security.web.csrf.CsrfFilter org.springframework.security.web.authentication.logout.LogoutFilter org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter org.springframework.security.web.savedrequest.RequestCacheAwareFilter org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter org.springframework.security.web.authentication.AnonymousAuthenticationFilter org.springframework.security.web.session.SessionManagementFilter org.springframework.security.web.access.ExceptionTranslationFilter org.springframework.security.web.access.intercept.FilterSecurityInterceptor

代码底层流程:重点看三个过滤器:

FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。

在这里插入图片描述

super.beforeInvocation(fi) 表示查看之前的 filter 是否通过。

fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务。

ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常

在这里插入图片描述

UsernamePasswordAuthenticationFilter :对/login的POST请求做拦截,校验表单中用户名,密码。
在这里插入图片描述

4.4、UserDetailsService 接口讲解

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

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersMapper usersMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用usersMapper方法,根据用户名查询数据库
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        // where username=?
        wrapper.eq("username",username);
        Users users = usersMapper.selectOne(wrapper);
        //判断
        if(users == null) {//数据库没有用户名,认证失败
            throw  new UsernameNotFoundException("用户名不存在!");
        }
        List<GrantedAuthority> auths =
                AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
        //从查询数据库返回users对象,得到用户名和密码,返回
        return new User(users.getUsername(),
                new BCryptPasswordEncoder().encode(users.getPassword()),auths);
    }
}
  • 返回值 UserDetails
// 表示获取登录用户所有权限
Collection<? extends GrantedAuthority> getAuthorities();
// 表示获取密码
String getPassword();
// 表示获取用户名
String getUsername();
// 表示判断账户是否过期
boolean isAccountNonExpired();
// 表示判断账户是否被锁定
boolean isAccountNonLocked();
// 表示凭证{密码}是否过期
boolean isCredentialsNonExpired();
// 表示当前用户是否可用
boolean isEnabled();

5、认证入门测试

导入依赖

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
    </dependencies>

配置类

public class SecurityConfigA extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置没有权限访问跳转自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin()   //自定义自己编写的登录页面
                .loginPage("/login.html")  //登录页面设置
                .loginProcessingUrl("/user/login")   //登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()  //登录成功之后,跳转路径
                .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
}

controller类

@RestController
@RequestMapping("/test")
public class UserController {

    @GetMapping("hello")
    public String hello(){
        return "hello,world";
    }
    @GetMapping("index")
    public String index(){
        return "hello,Index";
    }
}

service层

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersMapper usersMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用usersMapper方法,根据用户名查询数据库
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        // where username=?
        wrapper.eq("username",username);
        Users users = usersMapper.selectOne(wrapper);
        //判断
        if(users == null) {//数据库没有用户名,认证失败
            throw  new UsernameNotFoundException("用户名不存在!");
        }
        List<GrantedAuthority> auths =
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
        //从查询数据库返回users对象,得到用户名和密码,返回
        return new User(users.getUsername(),
                new BCryptPasswordEncoder().encode(users.getPassword()),auths);
    }
}

自定义login页面

    <form action="/user/login" method="post">
        用户名:<input type="text" name="username"/>
        <br/>
        密码: <input type="password" name="password"/>
        <br/>
        <input type="submit" value="login"/>
    </form>

6、基于角色或权限进行访问控制

  • hasAuthority方法
//当前登录用户,只有具备admins 权限才可以访问这个权限
.antMatchers("test/index").hasAuthority("admins");
//在UserDetailsService,把返回User对象设置权限
List<GrantedAuthority> auths =
                AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
  • hasAnyAuthority方法
.antMatchers("test/index").hasAnyAuthority("admins,manager");
  • hasRole方法
//如果用户具备给定角色,就允许访问,否则出现403,如果当前主体具有指定的角色,则返回true
.antMatchers("/test/index").hasRole("sale");
  • hasAnyRole方法
注解使用
  • Secured

用户具有某个角色,可以访问方法

  • PreAuthorize

在启动类中开启注解

@EnableGlobalMethodSecurity(secureEnabled=true,prePostEnabled = true)
public class Application{
}
在controller的方法上面添加注解

加上Roles前缀,前面支持role

  • PostFilter

对方法返回的数据进行过滤

用户注销
  1. 修改配置类,登录成功之后跳转到成功页面
  2. 在成功页面添加超链接,写设置退出路径
  3. 当登录成功之后,在成功页面点击退出,再去访问其他controller不能进行访问的
自动登录
  1. cookie技术
  2. 安全框架机制实现自动登录

实现原理

在这里插入图片描述

配置类,注入数据源,配置操作类

@Bean
public PersistentTokenRepository persistentTokenRepository(){
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    return jdbcTokenRepository;
}
页面添加记住我复选框
<input type= "checkbox" name= "remember-me" tetle="记住密码"/> 

在这里插入图片描述

CSRF理解

跨站请求伪造(英语:Cross-site request forgery),也被称为one-clickattack或者session riding,通常缩写为CSRF 或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。

跨站请求伪造:
//开启跨站请求伪造
http.csrf().disable();  
//在注册登录页面开启隐藏项
<input type="hidden" th:name="${_cstf.parameterName}" th:value="{_csrf.token}"/>

客户端(生成带token的字符串)——认证(生成token存储session或者cookie)——判断
F理解

跨站请求伪造(英语:Cross-site request forgery),也被称为one-clickattack或者session riding,通常缩写为CSRF 或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。

跨站请求伪造:
//开启跨站请求伪造
http.csrf().disable();  
//在注册登录页面开启隐藏项
<input type="hidden" th:name="${_cstf.parameterName}" th:value="{_csrf.token}"/>

客户端(生成带token的字符串)——认证(生成token存储session或者cookie)——判断

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值