Security拌面

SpringSecurity是一个基于Spring开发的非常强大的权限验证框架,其核心功能包括:

学习SpringSecurity的笔记

  • 认证 (用户登录)
  • 授权 (此用户能够做哪些事情)
  • 攻击防护 (防止伪造身份攻击)

简单环境配置

首先按照mvc的模板,写初始化

import org.example.config.WebConfiguration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{WebConfiguration.class};   //基本的Spring配置类,一般用于业务层配置
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[0];  //配置DispatcherServlet的配置类、主要用于Controller等配置,这里为了教学简单,就不分这么详细了,只使用上面的基本配置类
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};    //匹配路径,与上面一致
    }


}

写配置,thymeleaf,数据库

@EnableWebMvc
@ComponentScans({
       @ComponentScan("org.example")
})
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
    //我们需要使用ThymeleafViewResolver作为视图解析器,并解析我们的HTML页面
    @Bean
    public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine springTemplateEngine){
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setOrder(1);   //可以存在多个视图解析器,并且可以为他们设定解析顺序
        resolver.setCharacterEncoding("UTF-8");   //编码格式是重中之重
        resolver.setTemplateEngine(springTemplateEngine);   //和之前JavaWeb阶段一样,需要使用模板引擎进行解析,所以这里也需要设定一下模板引擎
        return resolver;
    }

    //配置模板解析器
    @Bean
    public SpringResourceTemplateResolver templateResolver(){
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setSuffix(".html");   //需要解析的后缀名称
        resolver.setPrefix("classpath:");   //需要解析的HTML页面文件存放的位置,默认是webapp目录下,如果是类路径下需要添加classpath:前缀
        resolver.setCharacterEncoding("UTF-8");  // 关键设置!
        return resolver;
    }

    //配置模板引擎Bean
    @Bean
    public SpringTemplateEngine springTemplateEngine(ITemplateResolver resolver){
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(resolver);   //模板解析器,默认即可
        return engine;
    }

    @Bean //单独创建一个Bean,方便之后更换
    public DataSource dataSource(){
        return new PooledDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/", "root", "1234");
    }
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ //直接参数得到Bean对象
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean;
    }
     //让 Spring MVC 用 FastJson 处理 JSON 格式的数据
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters){
        converters.add(new FastJsonHttpMessageConverter());
    }
}

写Controller和前端

@Controller
public class HelloController {
    //处理登录操作并跳转
    @PostMapping("/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        HttpSession session,
                        Model model){
        if("test".equals(username) && "123456".equals(password)) {
            session.setAttribute("login", true);
            return "redirect:/";
        } else {
            model.addAttribute("status", true);
            return "login";
        }
    }

    //处理首页或是登录界面跳转
    @GetMapping("/")
    public String index(HttpSession session){
        if(session.getAttribute("login") != null) {
            return "index";
        }else {
            return "login";
        }
    }
    @ResponseBody
    @PostMapping("/pay")
    public JSONObject pay(@RequestBody Map<String, String> requestMap,
                          HttpSession session){
        String account = requestMap.get("account");
        JSONObject object = new JSONObject();
        //登录之后才能转账
        if(session.getAttribute("login") != null) {
            System.out.println("转账给"+account+"成功,交易已完成!");
            object.put("success", true);
        } else {
            System.out.println("转账给"+account+"失败,用户未登录!");
            object.put("success", false);
        }
        return object;
    }
}

这里要显式指定参数名(​Java 编译默认不保留参数名,@RequestParam 需要知道前端传递的参数名(如 usernamepassword)才能正确映射到方法参数,不然会出现500错误)

  • @RequestBody → 前端发JSON(不要手动加headers)

  • @RequestParam → 前端发表单(用URLSearchParams),这里处理不好会出现415错误

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>白马银行 - 首页</title>
</head>
<body>

</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>登录白马银行</title>
</head>
<body>
<form action="login" method="post">
    <label>
        用户名:
        <input name="username" type="text">
    </label>
    <label>
        密码:
        <input name="password" type="password">
    </label>
    <button type="submit">登录</button>
</form>
<div th:if="${status}">登录失败,用户名或密码错误!</div>
</body>
</html>
<script>
    function pay() {
        const account = document.getElementById("account").value;
        axios.post('/mvc/pay', { account: account },{
            headers: {
                'Content-Type': 'application/json'
            }})
            .then(({data}) => {
                if(data.success)
                    alert("转账成功")
                else
                    alert("转账失败")
            })
    }
</script>

再添加security的初始化器和配置类

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
    //不用重写任何内容
  	//这里实际上会自动注册一个Filter,SpringSecurity底层就是依靠N个过滤器实现的,我们之后再探讨
}


@Configuration
@EnableWebSecurity   //开启WebSecurity相关功能
public class SecurityConfiguration {
		
}



在根容器中加入security的配置类
@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class[]{MainConfiguration.class, SecurityConfiguration.class};
}

Web攻击方式:
 

1. CSRF(跨站请求伪造)

简单理解:
攻击者诱导你在已登录的情况下,去访问另一个网站的接口,从而冒充你的身份进行操作。

举例:
你登录了银行网站A,没退出。此时访问了攻击者的网站B,B偷偷让你的浏览器请求A的转账接口,银行以为是你本人操作,钱就被转走了。


2. SFA(会话固定攻击,Session Fixation Attack)

简单理解:
攻击者提前拿到一个合法的会话ID,然后诱导你用这个ID登录,这样攻击者就能用同一个ID冒充你。

举例:
攻击者给你发一个带有特定sessionid的链接,你用这个链接登录后,攻击者用同样的sessionid访问网站,就能冒充你的身份。


3. XSS(跨站脚本攻击)

简单理解:
攻击者把恶意脚本代码注入到网站页面中,当其他用户访问时,这些代码会在他们的浏览器里执行,窃取信息或冒充操作。

举例:
攻击者在评论区发了一段恶意JS代码,其他人看到评论时,这段代码会自动运行,比如窃取他们的cookie。

内存验证

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean   //UserDetailsService就是获取用户信息的服务
    public UserDetailsService userDetailsService() {
      	//每一个UserDetails就代表一个用户信息,其中包含用户的用户名和密码以及角色
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")  //角色目前我们不需要关心,随便写就行,后面会专门讲解
                .build();
        UserDetails admin = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("password")
                .roles("ADMIN", "USER")
                .build();
        return new InMemoryUserDetailsManager(user, admin); 
      	//创建一个基于内存的用户信息管理器作为UserDetailsService
    }
}

这里会有登入界面,如果不登入,就会一直重定向到login界面,

现在去掉之前自己写的登入

@Controller
public class HelloController {

    //现在所有接口不需要任何验证了,因为Security已经帮我们做了,没登录是根本进不来的
    @GetMapping("/")
    public String index(){
        return "index";
    }

    @ResponseBody
    @PostMapping("/pay")
    public JSONObject pay(@RequestParam String account){
        JSONObject object = new JSONObject();
        System.out.println("转账给"+account+"成功,交易已完成!");
        object.put("success", true);
        return object;
    }
}

然后出现了403错误

这是因为SpringSecurity自带了csrf防护,需求我们在POST请求中携带页面中的csrfToken才可以,否则一律进行拦截操作,这里我们可以将其嵌入到页面中,随便找一个地方添加以下内容:

<input type="text" th:id="${_csrf.getParameterName()}" th:value="${_csrf.token}" hidden>

接着在axios发起请求时,携带这个input的value值: 

<script>
    function pay() {
        const account = document.getElementById("account").value;
        const _csrf = document.getElementById("_csrf").value;
        axios.post('/mvc/pay', { account: account,_csrf:_csrf },{
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': _csrf // 将 CSRF token 传递到请求头中

            }})
            .then(({data}) => {
                if(data.success)
                    alert("转账成功")
                else
                    alert("转账失败")
            })
    }
</script>

如果csrf没有配置好会出现403错误

由于密码是明文写在上面的,非常的不安全,所以我们需要加密

首先在security配置类中写入(可以使用官方提供的BCrypt加密工具)

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
    
  	//这里将BCryptPasswordEncoder直接注册为Bean,Security会自动进行选择
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        UserDetails user = User
                .withUsername("user")
                .password(encoder.encode("password"))   //这里将密码进行加密后存储
                .roles("USER")
                .build();
      	System.out.println(encoder.encode("password"));  //一会观察一下加密出来之后的密码长啥样
        UserDetails admin = User
                .withUsername("admin")
                .password(encoder.encode("password"))   //这里将密码进行加密后存储
                .roles("ADMIN", "USER")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值