Spring Security(基础笔记1)

6 篇文章 0 订阅
6 篇文章 0 订阅

前言

认证:

身份验证是验证用户身份的过程。也就是说,当用户通过应用程序进行身份验证时,他们在证明自己实际上就是他们所说的身份。

授权:

授权本质上是访问控制-控制用户可以在应用程序中访问的内容(例如资源,网页等)。大多数用户通过使用角色和权限等概念来执行访问控制。也就是说,通常根据分配给他们的角色和/或权限,允许用户执行某项操作或不执行某项操作。

一、Spring Security

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实标准。
Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。像所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义需求
为Spring IO Platform提供安全服务

二、特征

  • 对身份验证和授权的全面且可扩展的支持(认证 授权 )
  • 防止会话固定、点击劫持、跨站点请求伪造等攻击(安全)
  • Servlet API 集成
  • 与 Spring Web MVC 的可选集成

三、spring security 和 shrio的区别

相同点

1:认证功能
2:授权功能
3:加密功能
4:会话管理
5:缓存支持
6:rememberMe功能

不同点

优点

  1. spring Security基于spring 开发 如果是spring项目 配合spring security做权限根据更加方便 ,而shrio 需要和 spring进行整合开发
  2. spring Security 功能比Shiro更加丰富些 ,例如安全防护
  3. Spring Security 社区资源比shiro丰富
    缺点
  4. shrio配置和使用比较简单,spring Security 比较复杂
  5. shrio依赖性低 不需要任何框架和容器,可以独立运行 ,而 spring Security 依赖于spring容器

四、实例

入门示例

  1. 创建springboot项目 选择依赖
    在这里插入图片描述
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
  1. 编写一个类
 package com.aaa.sbs.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2022/2/14 9:05
 */
@RestController
@RequestMapping("hello")
public class HelloController {
    /**
     * 入门测试
     * @return
     */
    @GetMapping("helloSpringSecurity")
    public  String hello(){
        return "hello Security";
    }
}

一个方法就行

  1. 直接访问这个方法

http://localhost:18888/hello/helloSpringSecurity
加入security包后就需要认证后才可以访问,会跳转到自带登录页面
在这里插入图片描述

使用user 和 控制台打印的随机密码登录

4. 登录原码解析(使用debug)

当我们没有认证时,会请求一个默认过滤器DefaultLoginPageGeneratingFilter,在这里插入图片描述
查看doFilter方法可以看到,如果登录成功直接放行,如果没有登录会调用generateLoginPageHtml(request, loginError, logoutSuccess)方法,判断this.formLoginEnabled就会看到登录界面(在servlet中学习过)
在这里插入图片描述
在这里插入图片描述
当点击登录时,会再经过一个过滤器UsernamePasswordAuthenticationFilter,执行它中的(直接双击shift搜索)
在这里插入图片描述
进行认证,当认证成功会调用该过滤器的父类AbstractAuthenticationProcessingFilter中的successfulAuthentication方法,失败时会调用unsuccessfulAuthentication方法。
在这里插入图片描述
认证时系统默认的密码会通过UserDetailsServiceAutoConfiguration中的getOrDeducePassword获取或者推断密码方法打印到控制台,从中可以看出在SecurityProperties类中有一个内部类User,我们登录的用户名和密码都是通过该实体产生。
在这里插入图片描述

自定义用户名和密码

  1. 在application.properties配置文件中配置:
spring.security.user.name=admin
spring.security.user.password=tiger
  1. 使用配置类
package com.aaa.sbs.config;

import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2022/2/14 10:33
 */
@Configuration
@Log4j2
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //实例化 BCryptPasswordEnder
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //使用加密类生成加密密码
        String password = bCryptPasswordEncoder.encode("tiger");
        log.info("加密后的密码"+password);
        boolean matchesPassword = bCryptPasswordEncoder.matches("tiger",password);

        log.info("密码是否正确"+matchesPassword);
        //等于吧用户名密码直接配置 。使用时加载到内存中
        //withUser配置 用户名 scott 密码 tiger
        auth.inMemoryAuthentication().withUser("scott").password(password).roles("guanliyuan");

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

  1. 使用配置类加UserDetailsService(常用):
  2. 配置类
package com.aaa.sbs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;
 

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2022/2/14 10:32
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 配置获取用户信息接口 配置加密方式
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

  1. 服务类
package com.aaa.sbs.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @author zhangyifan
 * @version 8.0
 * @description: 相当与 shrio的realm
 * @date 2022/2/14 10:34
 */
@Service
public class WebDetailServiceImpl  implements UserDetailsService {
    //注入userService接口
/*
    * private UserService userService       */

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("guanliyuan,dailishang,shanghu,dept:query,dept:update");
        //第一个参数:数据库取出用户名   第二个参数:加密密码   第三个参数:角色/权限集合
        return  new User("root",new BCryptPasswordEncoder().encode("tiger"),grantedAuthorities);
    }
}

关键类

  1. UsernamePasswordAuthenticationFilter
    对登录信息进行拦截,检查校验表单中的用户名和密码的一个过滤器。
    attemptAuthentication 认证方法
    successfulAuthentication 认证成功方法
    unsuccessfulAuthentication 认证失败方法

  2. UserDetailsService
    查询数据库数据的一个接口,返回一个UserDetails,UserDetails的子类User就是我们要使用的用户信息类(包含用户名,密码,权限信息等)
    方法
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

  3. PasswordEncoder
    是Spring Security提供的密码加密方式的接口定义。
    String encode(CharSequence rawPassword);
    用来对明文密码进行加密,返回加密之后的密文。
    //一个密码校对方法,在用户登录的时候,将用户传来的明文密码和数据库中保存的密文密码作为参数,传入到这个方法中去,根据返回的Boolean 值判断用户密码是否输入正确。
    boolean matches(CharSequence rawPassword, String encodedPassword);
    //如果解析的密码能够再次进行解析且达到更 安全的结果则返回 true,否则返回 false。默认返回 false。
    default boolean upgradeEncoding(String encodedPassword) { return false; }

  4. Spring Security 提供了多种密码加密方案,
    官方推荐使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 SecureRandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。

不同于 Shiro 中需要自己处理密码加盐,在 Spring Security 中,BCryptPasswordEncoder 就自带了盐,处理起来非常方便。

package com.aaa.sbs.test;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author zhangyifan
 * @version 8.0
 * @description: 测试BCryptPasswordEncoder
 * @date 2022/2/14 15:37
 */
public class PasswordEncoderTest {
    public static void main(String[] args) {
        PasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //加密密码
        String password = bCryptPasswordEncoder.encode("tiger");
        System.out.println("密码"+password);
        //验证密码是否正确
        boolean isSuc = bCryptPasswordEncoder.matches("tiger",password);
        System.out.println("密码是否正确"+isSuc);
        boolean isSucl = bCryptPasswordEncoder.matches("tiger","$2a$10$kkB4WomyMeYRhg.iC6g0OubsrZGBvAfX7Cuq0mtDkIbMUpLTahDpy");
        System.out.println("比对过去生成的密码是否正确"+isSucl);
    }
}

在这里插入图片描述

自定义登录页面,用户登出和未认证授权的错误页面配置(403)

  1. 配置 WebSecuityConfig
package com.aaa.sbs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;


/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2022/2/14 10:32
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 配置获取用户信息接口 配置加密方式
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义用户登录,注销 角色 /权限 授权配置 都在该方法配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置登录页面信息
        http.formLogin()//总体form方式
                .loginPage("/html/login.html") //配置登录页面路劲
                .loginProcessingUrl("/user/login") //登录页面中form 配置的请求地址
                .failureUrl("/html/login.html?error")//登录失败路劲配置
                .defaultSuccessUrl("/html/indexl.html").permitAll()//默认登录成功的
                .and().authorizeRequests()//逻辑所有的授权请求
                .antMatchers("/user/login","/","/css/**","/js/**").permitAll()
                .anyRequest().authenticated()//除了上面配置的,其他请求都需要认证
                .and().csrf().disable();//关闭csrf概念 如果不关闭
        //配置未授权跳转页面
        http.exceptionHandling().accessDeniedPage("/html/unauthorized.html");
        //注销配置 如果使用默认 /logout 可以不用配置
        http.logout().logoutUrl("/logout")//用户请求注销 请求地址
                .logoutSuccessUrl("/html/login.html").permitAll();//注销成功跳转地址
    }
}

  1. 登录页面和未授权页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>员工登录</title>
    <script>
        function load(){
            var url = location.href;
            //判断url中是否含有?
            if(url.indexOf("?")!=-1){
                //                             原始js          jquery
                //原始JS赋值   <a>url</ a>     .innerText      .text()
                //                input       .value=        .val()
                //                 div        .innerHTML      .html
                document.getElementById("errorInfo").innerHTML="用户名或者密码错误!!";
            }
        }
    </script>
</head>
<body onload="load()">
<center>
    <h3>登录页面</h3>
    <form action="/user/login" method="post">
        <div id="errorInfo" style="color: red"></div>
        <table border="1" >
            <!--用户名和密码的name一定是 username password 大小写也有区分-->
            <tr><td>用户名</td><td><input type="text" name="username"> </td></tr>
            <tr><td>密码</td><td><input type="text" name="password"> </td></tr>
            <tr><td colspan="2" align="center"><input type="submit" value="登录"></td></tr>
        </table>
    </form>
</center>
</body>
</html>

unauthorized.html(注意页面名称中不能含有-否则404)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误页面</title>
</head>
<body>
访问了未授权的地址。。。。。。。。。。。

</body>
</html>
  1. 测试
    n:root
    p:tiger
    直接访问刚才的hello方法

直接访问,看是否访问了已经访问的页面
先请求登录
在另外窗口退出
再访问原来页面,发现需要登录,说明已经退出

授权操作

1. hasAuthority(有权限)和 hasAnyAuthority (有任何权限)用法

编写模拟控制器

@RestController
@RequestMapping("dept")
public class DeptController {
    /**
     * 模拟部门查询
     * @return
     */
    @GetMapping("queryAll")
    public String queryAll(){
        return "模拟部门查询";
    }
    }

授权配置
在这里插入图片描述

服务配置

@Service
public class WebDetailServiceImpl  implements UserDetailsService {
    //注入userService接口
/*
    * private UserService userService       */

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> grantedAuthorities =
                AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_guanliyuan,ROLE_dailishang,shanghu,dept:query,dept:update");
        //第一个参数:数据库取出用户名   第二个参数:加密密码   第三个参数:角色/权限集合
        return  new User("root",new BCryptPasswordEncoder().encode("tiger"),grantedAuthorities);
    }
}

如果配置拥有某一个或者在多个权限中任意一个,就可以访问,否则不可以

hasRole(有角色)和hasAnyRole(有任何角色)方法
  1. 模拟控制器
  /**
     * 根据编号查部门
     * @param deptNo
     * @return
     */
    @GetMapping("queryById")
    public String queryById(Integer deptNo){
        return "根据编号查询部门";
    }
  1. 权限配置
 // .antMatchers("/dept/queryById").hasRole("guanliyuan")//拥有管理角色访问该路径
              .antMatchers("/dept/queryById").hasAnyRole("guanliyuan","dailishang")//设置拥有多个中某一个角色,才可以访问
 
  1. 角色
 @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> grantedAuthorities =
                AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_guanliyuan,ROLE_dailishang,shanghu,dept:query,dept:update");
        //第一个参数:数据库取出用户名   第二个参数:加密密码   第三个参数:角色/权限集合
        return  new User("root",new BCryptPasswordEncoder().encode("tiger"),grantedAuthorities);
    }

测试
http://localhost:18888/dept/queryById?deptNo=1

hasIpAddress
  1. 编写模拟控制器
  @GetMapping("add")
    public String add(){
        return "模拟部门添加";
    }
  1. 配置
    .antMatchers("/dept/add").hasIpAddress("192.168.220.1")//固定的ip才允许

http://localhost:18888/dept/add
在这里插入图片描述
http://192.168.220.1:18888/dept/add(正确)
在这里插入图片描述

授权注解
@Secured

验证当前用户是否具备某一角色,拥有可以访问,否则不可以。
注意
当@EnableGlobalMethodSecurity(securedEnabled=true)的时候,@Secured可以使用
在这里插入图片描述

  /**
     * 模拟用户查询
     * @return
     */
    //这两个有任意一个就可以访问
    @Secured({"ROLE_guanliyuan","ROLE_dailishang"})
    @GetMapping("queryAll")
    public String queryAll(){
        return "模拟用户查询";
    }

说明:拥有商户或者代理商角色的用户都可以方法queryAll方法。另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。

如果我们要求,只有同时拥有商户和代理商的用户才能方法queryAll()方法,这时候@Secured就无能为力了。
修改UserDetailServiceImpl 中的AuthorityUtils.commaSeparatedStringToAuthorityList(“dept:update,ROLE_dailishang,ROLE_shanghu”);进行测试

@ PreAuthorize

该注解在方法执行之后进行校验。
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限,Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PostAuthorize可以使用:
在这里插入图片描述

 //拥有一个权限就可以
    //@PreAuthorize("hasRole('ROLE_guanliyuan')")
    //两个必须都有
    //@PreAuthorize("hasRole('ROLE_guanliyuan') and hasRole('ROLE_dailishang')")
    //任意拥有一个就可以
    //@PreAuthorize("hasAnyRole('ROLE_guanliyuan','ROLE_dailishang')")
    //该方法可以通过returnObject获取到返回值做判断
    @PostAuthorize("returnObject!=null")
    @GetMapping("add")
    public String add(){
        return "模拟用户添加";
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Network porter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值