美文网首页
Spring Security入门案例

Spring Security入门案例

作者: 文景大大 | 来源:发表于2021-07-30 19:27 被阅读0次

    一、简介

    Spring Security是一个高度自定义的安全框架。利用Spring IoC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。主要包含如下几个重要的内容:

    • 认证(Authentication),系统认为用户是否能登录。
    • 授权(Authorization),系统判断用户是否有权限去做某些事情。

    二、入门案例

    首先引入必要的依赖:

    <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>
    

    然后创建一个controller:

    @Slf4j
    @RestController
    public class SecurityController {
    
        @GetMapping({"/", "/index"})
        public String getLogin() {
            log.info("进入index");
            return "index";
        }
    
    }
    

    此时,我们的入门案例就完成了。启动项目,Spring Security默认就开启了,此时访问localhost:8080/index就会被Spring Security拦截,跳转到内置的登录页面要求登录。

    默认情况下,登录的用户名为user,密码在启动项目的时候,控制台有打印出来:

    Using generated security password: 0bfad04b-7a47-40fb-ae15-2a4a7c57099b
    

    使用如上的账密登录后,再次访问localhost:8080/index就可以正常返回预期的内容index了。

    如果我们不希望使用默认的用户密码,可以在配置文件中指定一个,如此Spring Security就会使用我们指定的,而不会使用默认的了。

    spring.security.user.name=zhangxun
    spring.security.user.password=123123
    

    三、自定义认证逻辑

    当我们开启自定义认证逻辑后,上面的默认用户和配置文件中的用户就不生效了,可以先行删除。

    我们新建一个MySecurityConfig类,并继承WebSecurityConfigurerAdapter类,用于自定义认证逻辑。

    @EnableWebSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 指定使用BCryptPasswordEncoder对前端传过来的明文密码进行encode
            PasswordEncoder encoder = new BCryptPasswordEncoder();
            // 用户的真实密码需要encode,security是比较两个密文是否一致
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("root").password(encoder.encode("root123")).roles();
        }
    
    }
    

    需要注意的是,密码必须使用如上的PasswordEncoder进行编码,否则会抛出如下错误:

    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    

    此时,我们重启应用,访问localhost:8080/index进入内置的登录页面,输入root/root123之后就能正常返回index内容了。

    四、自定义授权逻辑

    一般权限管理都是基于RBAC模型的,即登录的用户肯定拥有某些角色,这些角色允许访问某些资源。我们先来改造下认证的逻辑:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
            .withUser("root").password(encoder.encode("root123")).roles("admin","manager")
            .and()
            .withUser("manager").password(encoder.encode("mm000")).roles("manager");
    }
    

    使得root用户拥有admin和manager两个角色,zhang用户拥有manager一个角色。

    然后我们在该配置类中再增加自定义授权的逻辑:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 任何角色允许访问
            .antMatchers("/", "/index").permitAll()
            // 仅admin角色可以访问
            .antMatchers("/admin/**").hasRole("admin")
            // admin和manager两个角色可以访问
            .antMatchers("/manager/**").hasAnyRole("admin", "manager");
    
        // 没有权限则进入内置的登录页面
        http.formLogin();
    }
    

    然后为了测试,我们还需要增加几个资源:

    @Slf4j
    @RestController
    public class SecurityController {
    
        @GetMapping({"/", "/index"})
        public String getLogin() {
            log.info("进入index");
            return "index";
        }
    
        @GetMapping("admin/getHello")
        public String getAdminHello(){
            return "hello admin!";
        }
    
        @GetMapping("manager/getHello")
        public String getManagerHello(){
            return "hello manager!";
        }
    
        @GetMapping("guest/getHello")
        public String getGuestHello(){
            return "hello guest!";
        }
    
    }
    

    此时,重启项目,我们发现:

    • 访问/,/index,/guest/**的资源直接就能返回,不需要认证和授权。
    • 访问/admin/**资源的时候,由于没有登录,会跳转到内置的登录页面;如果已经登录,只有root用户登录后才可以访问;
    • 访问/manager/**资源的时候,由于没有登录,会跳转到内置的登录页面;如果已经登录,那么root和zhang用户都能访问;

    我们还可以定制自己的登录页面,用于替换Spring Security内置的登录页面,这块需要定制html页面,本文不再详述,比较简单,可以参考formLogin的源码注释,里面讲的比较清楚。

    五、注销登录

    因为我们使用的是Spring Security内置的登录页面,各个资源返回的也是json字符串,并非页面,所以如何实现注销登录是个问题。但可以通过阅读HttpSecurity:logout中的源码注释,我们基本就能学会怎么操作了。

    • 注销登录默认就开启了,默认是访问/logout,和/login一样都是Spring Security自己实现的,我们调用即可;
    • 注销登录会清除服务器端的session,清除remember me等设置;这个后面再详细解说;
    • 注销登录后默认会跳转到/login页面;

    还是如上的案例,我们在登录后,直接调用http://localhost:8080/logout就可以实现上述的注销登录功能了。

    但是在有些时候,我们会自定义登出的URL以及成功登出后应该跳转到哪个URL,Spring Security也支持我们进行自定义。

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    // 任何角色允许访问
                    .antMatchers("/", "/index").permitAll()
                    // 仅admin角色可以访问
                    .antMatchers("/admin/**").hasRole("admin")
                    // admin和manager两个角色可以访问
                    .antMatchers("/manager/**").hasAnyRole("admin", "manager");
    
            // 没有权限进入内置的登录页面
            http.formLogin();
            // 自定义登出逻辑
            http.logout().logoutUrl("/myLogOut").logoutSuccessUrl("/index");
        }
    

    当Post方式请求/myLogOut的时候就会触发Spring Security的登出逻辑,并且登出后会跳转到/index界面。

    注意:在本案例中,是使用浏览器进行测试的,而且没有html的页面,所以使用浏览器发起post请求比较困难,那么使用get请求发起可以吗?默认是不行的,因为Spring Security默认开启了CSRF校验,所有改变状态的请求都必须以POST方式提交,为了能验证我们这个例子,我们需要把CSRF校验关掉,即在如上logout代码后面加上如下的配置:

    // 暂时关闭CSRF校验,允许get请求登出
    http.csrf().disable();
    

    此时再重启应用,就可以验证localhost:8080/myLogOut的登出逻辑了。

    六、记住我功能

    当我们没有开启记住我功能的时候,登录root用户后,如果关掉浏览器,重新打开网址,会发现登录已经退出了,这是因为登录信息只在当前会话有效。

    如果我们想要在某个时间段以内,一直使root用户处于登录状态,那么就需要在浏览器端设置一个cookie,在有效期内,这个cookie所属的用户就一直是登录的状态。同样的,只要在上面注销登录的代码后面加上:

    // 开启remember me功能,有效期默认14天
    http.rememberMe();
    

    此时内置的登录页面会出现记住我的选择框,当我们选择上登录后,浏览器端就会有当前用户的cookie信息了(名称为remember-me),在它过期之前,登录状态就一直有效。

    需要用户主动退出登录,也就是调用我们上面的/myLogOut才能将cookie清除并退出登录。

    如果是自定义的登录页面,可以在后面链式调用rememberMeParameter()方法,传入自己的rememberme参数名称即可。

    以上是关于Spring Security的基本使用方法,使用数据库及其它特性将会在后面的文章中予以说明。

    七、会话管理

    在以上例子中,认证和授权都是Spring Security自动进行的。但是有的时候我们需要管理会话,比如从会话中获取用户姓名、用户的权限信息;会话策略选择以及会话超时设置等。

    我们只需要增加如下的方法即可:

        private String getName(){
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            Object principal = authentication.getPrincipal();
            if(principal == null){
                return "游客";
            }
            if(principal instanceof UserDetails){
                UserDetails userDetails = (UserDetails) principal;
                return userDetails.getUsername();
            } else{
                return principal.toString();
            }
        }
    

    该方法使用SecurityContextHolder获取上下文信息,然后再获取到其中的用户名即可,当然其中还提供了可以获取密码、权限信息等方法。

    Session的管理策略有以下几种:

    • always,如果没有Session就会创建一个;
    • ifRequired,登录时如果有需要,就创建一个;
    • never,不会主动创建session,如果其它地方创建了session,就会使用它;
    • stateless,不会创建也不会使用session;

    其中ifRequired是默认的模式,stateless是采用token机制时,session禁用的模式,设置方法如下:

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        }
    

    至于session的超时和安全可以在配置文件中设置:

    # 超时时间设置
    server.servlet.session.timeout=3600s
    # 浏览器脚本将无法访问cookie
    server.servlet.session.cookie.http‐only=true
    # cookie将仅通过HTTPS连接发送
    server.servlet.session.cookie.secure=true
    

    相关文章

      网友评论

          本文标题:Spring Security入门案例

          本文链接:https://www.haomeiwen.com/subject/bxyqvltx.html