Spring Boot学习笔记-Spring Security(一)

Spring Security

简介

Spring Security是一个能够为基于Spring生态圈,提供安全访问控制解决方案的框架。它提供了一组可以在Spring应用上下文中配置的机制,充分利用了Spring的强大特性,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

使用

引入Spring Security依赖:

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

一旦引入此依赖后,不需要任何配置,启动项目后访问链接就会需要账号密码登录。
默认账号是:user
每次启动项目生成的密码都不一样,可在项目启动打印日志(需要级别是INFO或以下)中找到:在这里插入图片描述

配置方式

使用配置文件配置

有了安全配置的属性,即使没有加入 @EnableWebSecurity ,Spring Boot也会根据配置的项自动启动安全机制。

spring:
  security:
    user:
      # 用户名,默认user
      name: user
      # 用户密码
      password: 123456
      # 用户角色
      #roles:
    #filter:
      # 过滤器排序
      #order:
      # 安全过滤器责任链拦截的分发类型
      #dispatcher-types:
继承WebSecurityConfigurationAdapter
基于内存的认证

新建MyWebSecurityConfig类,继承自WebSecurityConfigurerAdapter:

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        //不操作密码编码器,方便调试使用
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于内存的用户配置在配置角色时不需要添加“ROLE_”前缀
        auth.inMemoryAuthentication().withUser("admin").password("123").roles("Role1", "Role2")
                .and()
                .withUser("user").password("321").roles("Role1");
    }
}
HttpSecurity

根据角色来管理受保护的资源,重构configure方法:

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        //不操作密码编码器,方便调试使用
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于内存的用户配置在配置角色时不需要添加“ROLE_”前缀
        auth.inMemoryAuthentication().withUser("admin").password("123").roles("Role1", "Role2")
                .and()
                .withUser("user").password("321").roles("Role1");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //调用authorizeRequests()方法开启HttpSecurity的配置
        http.authorizeRequests()
                // 访问 “/invoke1”模式的URL必须具备Role1的角色
                .antMatchers("/invoke1")
                .hasRole("Role1")
                // 访问 “/invoke2”模式的URL必须具备Role1或Role2的角色
                .antMatchers("/invoke2")
                .access("hasAnyRole('Role1','Role2')")
                // 访问 “/invoke3/**”模式的URL必须具备Role1或Role2的角色
                .antMatchers("/invoke3/**")
                .access("hasRole('Role1') and hasRole('Role2')")
                // 表示除了前面定义的URL模式外,用户访问其他的URL都必须认证(登录)后访问
                .anyRequest()
                .authenticated()
                //开启表单登录,即一开始的登录页面
                .and()
                .formLogin()
                //配备登录接口为“/login”,发起一个POST请求进行登录,登录参数中用户名默认为username,密码默认是password
                .loginProcessingUrl("/login")
                //permitAll:表示和登录相关的接口都不需要认证即可访问
                .permitAll()
                .and()
                //关闭csrf
                //跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
                .csrf()
                .disable();
    }
}

controller测试类:

@RestController
public class TestController {

    @GetMapping("invoke")
    public String invoke() {
        return "success";
    }

    @GetMapping("invoke1")
    public String invoke1() {
        return "invoke1";
    }

    @GetMapping("invoke2")
    public String invoke2() {
        return "invoke2";
    }

    @GetMapping("invoke3")
    public String invoke3() {
        return "invoke3";
    }
}

登录表单详细配置

主要配置如下:

                //开启表单登录,即一开始的登录页面
                .and()
                .formLogin()
                // 配置登录页面,配置loginPage后,如果用户未获授权就访问一个需要授权才能访问的接口,就会自动跳转到“login_page”
                //页面让用户登录,login_page为开发者自定义的页面,不再是默认登录页
                .loginPage("/login_page")
                // 配备登录接口为“/login”,发起一个POST请求进行登录,登录参数中默认用户名为username,默认密码是password
                .loginProcessingUrl("/login")
                //定义认证所需的用户名和密码的参数名,默认用户名为username,默认密码是password
                .usernameParameter("name")
                .passwordParameter("passwd")
                //定义登录成功的处理逻辑。用户登录成功后,可跳转到某一个页面,也可返回一段json。Authentication可以获取当前登录用户的信息
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response
                            , Authentication authentication) throws IOException, ServletException {
                        //获取当前登录用户的信息
                        Object principal = authentication.getPrincipal();
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        response.setStatus(200);
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 200);
                        map.put("msg", principal);
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                //定义登录失败的处理逻辑。登录失败后,可跳转到某一个页面,也可返回一段json。AuthenticationException参数可以获取登录失败的原因。
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response
                            , AuthenticationException exception) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        response.setStatus(401);
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 401);
                        if (exception instanceof LockedException) {
                            map.put("msg", "账户被锁定,登陆失败");
                        } else if (exception instanceof BadCredentialsException) {
                            map.put("msg", "账户名或密码输入错误,登录失败");
                        } else if (exception instanceof DisabledException) {
                            map.put("msg", "账户被禁用,登录失败");
                        } else if (exception instanceof AccountExpiredException) {
                            map.put("msg", "账户已过期,登录失败");
                        } else if (exception instanceof CredentialsExpiredException) {
                            map.put("msg", "密码已过期登录失败");
                        } else {
                            map.put("msg", "登陆失败");
                        }
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                //permitAll:表示和登录相关的接口都不需要认证即可访问
                .permitAll()
                .and()

使用postman测试时,需要使用POST方式请求:
在这里插入图片描述

注销登录配置

主要配置如下:

                .and()
                // 开启注销登录的配置
                .logout()
                // 配置注销登录请求URL为“/logout”,默认值也是“/logout”
                .logoutUrl("/logout")
                // 是否清除身份认证信息,默认为true,表示清除
                .clearAuthentication(true)
                // 是否使Session失效,默认为true
                .invalidateHttpSession(true)
                // 配置一个LogoutHandler,开发者可以在LogoutHandler中完成一些数据清除工作。
                .addLogoutHandler((request, response, authentication) -> {
                })
                //配置一个LogoutSuccessHandler,可以在这里处理注销成功后的业务逻辑,例如:返回json或跳转页面
                .logoutSuccessHandler((request, response, authentication) -> {
                    response.sendRedirect("/login_page");
                })

LogoutHandler更多实现:
在这里插入图片描述

在IDEA中由接口查看实现类图:快捷键 crtl + H 查看hierarchy,只能查看向上向下继承关系,而不能看实现了哪些接口。右键选择Diagrams(也可以使用快捷键ctrl+alt+u,更快捷), crtl + alt + B会显示出跟这个接口有关系的类,然后 全选+enter 即可展示全部类图。

多个HttpSecurity

如果业务比较复杂,也可以配置多个HttpSecurity,实现WebSecurityConfigurerAdapter的多次扩展。

/**
 * 配置多个HttpSecurity时,MultiHttpSecurityConfig不需要继承WebSecurityConfigurerAdapter,
 * 在MultiHttpSecurityConfig中创建静态内部类继承WebSecurityConfigurerAdapter即可,静态内部类上
 * 添加@configuration注解和@Order(表示该配置的优先级,数字越小优先级越大,为加@Order的优先级最小)注解
 */
@Configuration
public class MultiHttpSecurityConfig {

    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin").password("123").roles("Role1", "Role2")
                .and()
                .withUser("user").password("123").roles("Role1");
    }

    /**
     * 主要用来处理"invoke/**"模式的URL
     */
    @Configuration
    @Order(1)
    public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("invoke/**").authorizeRequests().anyRequest().hasRole("Role1");
        }
    }

    /**
     * 用于处理其他的Url
     */
    @Configuration
    public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/login")
                    .permitAll()
                    .and()
                    .csrf()
                    .disable();
        }
    }
}

配置多个HttpSecurity时,MultiHttpSecurityConfig不需要继承WebSecurityConfigurerAdapter,在MultiHttpSecurityConfig中创建静态内部类继承WebSecurityConfigurerAdapter即可,静态内部类上添加@configuration注解和@Order(表示该配置的优先级,数字越小优先级越大,为加@Order的优先级最小)注解

密码加密

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

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(10);
    }
  • 加密密码:
    BCryptPasswordEncoder bCryptPasswordEncoder=new BCryptPasswordEncoder(10);
    String password = bCryptPasswordEncoder.encode("password");

方法安全

除了基于URL认证与授权,也可以通过注解来灵活地配置方法安全

  1. 通过 @EnableGlobalMethodSecurity 注解开启基于注解的安全配置:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class WebSecurityConfig {
}
  • prePostEnabled = true 会解锁 @PreAuthorize@PostAuthorize 两个注解。@PreAuthorize会在方法执行前进行验证,@PostAuthorize会在方法执行后进行验证。
  • securedEnabled = true 会解锁 @Secured 注解
  1. 开启注解安全配置后,新建一个MethodService类进行测试:
@Service
public class MethodService {

    /**
     * 注解@Secured("ROLE_ADMIN")表示访问该方法需要ADMIN角色,需要在角色前加一个前缀"ROLE_"
     * @return
     */
    @Secured("ROLE_ADMIN")
    public String admin() {
        return "hello admin";
    }

    @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
    public String dba() {
        return "hello dba";
    }

    @PostAuthorize("hasAnyRole('ADMIN','DBA')")
    public String user() {
        return "hello user";
    }
}
  • 注解@Secured(“ROLE_ADMIN”)表示访问该方法需要ADMIN角色,需要在角色前加一个前缀"ROLE_"。
    测试时,需要使用post登录测试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值