zoukankan      html  css  js  c++  java
  • SpringInAction 第四章笔记 保护Spring

    项目地址:https://github.com/AganRun/SpringInAction/tree/master/Chapter4/taco-security

    第四章 配置Spring Security

    初次使用

    当加入了starter启动器后,项目启动时,在日志中会出现一个账号及随机密码

    Using generated security password: cf18a7e9-ffa6-429f-9331-40d2dd121f53
    

    此时登录项目会有一个登录页面,输入账号密码才能访问

    security starter的影响

    • 所有HTTP请求都需要认证,认证过程是通过HTTP basic认证对话框实现的
    • 没有特定的角色及权限,只有一个user用户
    • 没有登录页面

    配置Security

    只有一个用户显然满足不了需求,security有四种配置用户的方式

    • 基于内存的用户存储 (将账号密码直接硬编码到配置代码中,优点:方便快捷,可以用来调试。缺点:项目上线后不方便修改用户)
    • 基于JDBC的用户存储
    • 以LDAP作为后端的用户存储
    • 自定义用户详情服务

    JDBC配置Security

    当配置了数据源之后,Spring有一套默认的用户搜索SQL。若表于其不匹配,则可以自定义SQL去配置用户。

    酱默认的SQL查询替换为自定义的设计时,很重要的一点是遵循查询的基本协议。所有查询将用户名作为唯一参数

    @Configuration
    @EnableWebSecurity
    public class SecurityJdbcConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        DataSource dataSource;
        
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .jdbcAuthentication()
                    .dataSource(dataSource)
                    .usersByUsernameQuery(
                            "select username, password, enabled from Users where username = ?"
                    )
                    .authoritiesByUsernameQuery(
                                "select username, authority from UserAuthorities where username = ?"
                    );
        }
    }
    

    密码加密

    数据库明文存储密码是很危险的,passwordEncoder()方法可以接受任意的PasswordEncoder接口的实现

    • BCryptPasswordEncoder: 使用bcrypt强哈希加密
    • NoOpPasswordEncoder: 不进行任何转码(已过期)
    • Pbkdf2PasswordEncoder: 使用PBKDF2加密
    • SCryptPasswordEncoder: 使用scrypt哈希加密
    • StandardPasswordEncoder: 使用SHA-256哈希加密

    逻辑是:数据库中的密码应该永远不会被解密。将输入的密码进行算法转码,然后与库中的密码对比。

    @Bean
    public static PasswordEncoder passwordEncoder() {
        //自定义一个加密流程
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                //加1加密,哈哈
                System.out.println("encode" + charSequence);
                return charSequence.toString() + 1;
            }
    
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                System.out.println("match,charSequence:" + charSequence + ", dbPassword:" + s);
                return encode(charSequence).equals(s);
            }
        };
    }
    
    
    DB: user1/12341
    输入: user1/1234
    
    控制台输出
    match,charSequence:1234, dbPassword12341
    encode1234
    

    自定义用户认证

    1. 自定义一个实体类,例如User,去实现UserDetails接口
    2. 定义对应的Repository,Service(需要实现UserDetailsService的loadUserByUsername(String username))
    3. 配置类中,auth.userDetailsService(注入的service)

    保护Web请求

    在WebSecurityConfigureAdapter的Configure(HttpSecurity http)方法中
    可以配置的功能有:

    • 为某个请求提供服务之前,先预先满足条件
    • 配置自定义的登录页
    • 支持用户退出应用
    • 预防跨站请求伪造

    保护请求 & 登录

    可以通过配置路径与对应的角色控制,并指定登录页

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/design", "/orders")
    //                    .hasRole("USER")
                    .access("hasRole('USER')")     // design 和 orders 路径,需要有user角色
                .antMatchers("/", "/**")
    //                    .permitAll();
                    .access("permitAll")    //对于/和/**路径不拦截
            .and()
                .formLogin()
                    .loginPage("/login")   //登录页面
                    .defaultSuccessUrl("/design")  //成功后重定向到design页面
            ;
    }
    

    存在很多的配置方法,列举其中的几个

    方法 能做什么
    access(String) 如果给定的SpEL表达式计算结果为TRUE,就允许访问
    authenticated() 允许认证过的用户访问
    denyAll() 无条件拒绝所有访问
    hasIpAddress(String) 如果请求来自给定的IP地址,允许访问
    hasRole(String) 如果用户具备指定的角色,允许访问
    not() 对其他方法的结果求反
    permitAll() 无条件允许访问
    rememberMe() 如果用户通过Remember-me认证的,允许访问

    SpEl表达式可以编写复杂的逻辑。例如只允许具备ROLE_USER权限的用户,在星期二下午创建新的Taco

    退出

    .and().logout().logoutSuccessUrl("/");
    

    POST请求403?

    在上述配置完之后,即使用户已经登录了,在提交订单,请求/design时依旧会被拦截,页面403。
    那是因为SpringSecurity默认是开启了内置的CSRF保护, 建议不要关掉。先了解一下什么是CSRF。

    CSRF

    跨站请求伪造(Cross-Site Request Forgery, CSRF)。
    它会让用户在一个恶意Web页面填写信息,然后自动将表单以攻击受害者的身份提交到另外一个应用上。

    可以理解为:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

    预防措施 :应用在展现表单的时候,生成一个CSRF token,放在隐藏域存储起来,在提交表单的时候将token一起发送至服务器,由服务器对比匹配。

    可以加入隐藏域(JSP、Thymeleaf默认生成)

    <input type="hidden" name="_csrf" th:value="${_csrf.token}" />
    

    提交表单时,使用th:action属性

    <form method="POST" th:action="@{/login}" id="loginForm" >
    

    了解用户是谁

    如何获取当前用户的信息

    • 注入Principal对象到控制器
    • 注入Authentication对象到控制器
    • 使用@AuthenticationPricipal注解标注方法
    • 使用SecurityContextHolder来获取安全上下文
    public String processDesign(@Valid Taco taco, Errors errors, @ModelAttribute Order order, Principal principal) {
        String username = principal.getName()   //获取用户的username=>获取用户信息  缺点:业务中会掺杂安全代码
    
    public String processDesign(@Valid Taco taco, Errors errors, @ModelAttribute Order order, Authentication authentication) {
        User user = (User) authentication.getPrincipal();       //强转User对象
    
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        User user = (User) authentication.getPrincipal();       //繁琐,但是任何地方都可以,不限制在Controller中
    
    public String processDesign(@Valid Taco taco, Errors errors, @ModelAttribute Order order, @AuthenticationPrincipal User user) {
        //使用注解,最方便
    }
    
  • 相关阅读:
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    基于分布式锁解决定时任务重复问题
    基于Redis的Setnx实现分布式锁
    基于数据库悲观锁的分布式锁
    使用锁解决电商中的超卖
  • 原文地址:https://www.cnblogs.com/AganRun/p/13155168.html
Copyright © 2011-2022 走看看