在SpringBoot 中使用Security安全框架

网上有很多关于SpringBoot中使用SpringSecurity安全框架的教程,但是都是讲的不够清晰易懂,首先来讲,为什么我们要使用SpringSecurity,以前没有这个框架的时候,我们要想实现权限页面拦截,都采用的拦截器(interceptor)或者过滤器(filter),特别的麻烦,在使用SpringSecurity后,一切都变的很简单,只需要几个简单的配置就能实现权限身份认证已经对数据进行加密等,非常的方便,无需自己去写复杂的逻辑来实现以前采用拦截器和过滤器才能实现的功能,SpringSecurity正是 带了Spring的前缀所以,它非常的出名,在Spring.io官网它被列为一个单独的项目。也在不停的更新,SpringSecurity也能被整合到任何项目中,可谓是非常强大。
在本篇文章中只讲关于SpringSecurity安全框架的使用,不会涉及到任何数据库的操作,会讲SpringSecurity的基本原理,但是不会深入讲SpringSecurity的底层。

如何使用SpringSecurity安全框架

一、首先在我们项目的pom.xml中引入SpringSecurity的核心依赖,本教程使用的是SpringBoot最新版本2.1.4

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

当你引入这两个依赖的时候,SpringSecurity已经帮你进行了简单的配置,你重新运行项目的时候会发现在控制台有一串随机的密码
在这里插入图片描述
在项目中引入了SpringSecurity的依赖,默认会给你自动配置,默认的用户名是user,密码是每次启动控制台的一串字符串,每次启动这个密码都不一样。并且还会给你生成一个默认login登录页面
在这里插入图片描述
关于这个页面,这个页面是SpringSecurity官方提供的,但是没有人会使用这个页面来进行身份认证,都是自定义登录的页面来进行身份认证,但是处理登录逻辑是使用的这个接口

二、创建包开始写代码,项目结构如下
在这里插入图片描述
要想使用它,我们要完成 SpringSecurity最基本的配置,首先我们创建一个类WebSecurityConfg来继WebSecurityConfigurerAdapter这个适配器,关于这个适配器我不做深入的探讨,继承之后实现http的configure方法。

public class WebSecurityConfg extends WebSecurityConfigurerAdapter

这里注意的是,由于是一个配置类,我们需要在类上面加入配置的注解

@Configuration

实现HttpSecurity的configure方法
在这里插入图片描述
注意这里实现的是configure方法是HttpSecurity的方法
在这里插入图片描述
只是默认实现的话,我们还不能使用,我们还需要进行简单的配置才能够让SpringSecurity发挥强大的作用。

  @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
              .antMatchers("/500").permitAll()
              .antMatchers("/403").permitAll()
              .antMatchers("/404").permitAll()
              .anyRequest() //任何其它请求
              .authenticated() //都需要身份认证
              .and()
              .formLogin() //使用表单认证方式
              .loginProcessingUrl("/login")//配置默认登录入口
              .and()
              .csrf().disable();
   }

authorizeRequests定义那些url需要被保护,那些不需要进行保护,通常这个出来在配置的第一行,其中 antMatchers是设置路径,通常这里设置的是控制器(controller)上的请求路径,后面的permitAll是允许任何人访问,没有任何限制,上面的500、403、404我都设置的是任何人可以访问,通常对公共的页面我们都设置的permitAll来进行访问的。
下面的anyRequest意思除了以上的请求,authenticated的意思是需要身份认证,那么anyRequest和authenticated合起来就是除了上面的500、403、404 请求不需要进行身份认证,其它的请求全部都需要进行身份认证后才能访问。
and()方法类似于xml配置中的结束标签,and()方法返回的对象还是HttpSecurity,当我们配置完成一段配置就要使用and来结束
formLogin是采用的是表单认证方式,还有一个httpBasic认证,你应用应该不会用httpBasic进行认证吧,现在都是采用的formLogin来进行认证的。
csrf.disable是 关闭跨站请求伪造保护
loginProcessingUrl是配置默认的登录入口(这里强调一遍Spring security默认的处理登录接口是/login这个自带的接口)
我已经准备了404、403、500等页面


    @GetMapping("/404")
    public String notFoundPage() {
        return "404";
    }

    @GetMapping("/403")
    public String accessError() {
        return "403";
    }
    
    @GetMapping("/500")
    public String internalError() {
        return "500";
    }
    
    @GetMapping("/success")
    @ResponseBody
    public String success(){
        return "认证成功,进入success成功";
    }
    
    @GetMapping(value = "/user/login")
    private String loginPage(){
        return "login";
    }
    
    @GetMapping(value = "/person")
    public String personPage(){
        return "person";
    }
    
    @GetMapping(value = "/admin/index")
    public String adminPage(){
        return "admin/admin";
    }

在resource目录下的templates目录中放置这些页面,并适当的加入一些内容
在这里插入图片描述
当我们访问500、403、404页面的时候由于设置了permitAll,任何人都可以访问,所以访问是没有任何问题。
在这里插入图片描述
但是当我们访问一个除500、404、403的请求,那么会被重定向登录页,要求进行身份认证,身份认证成功后才能访问
在这里插入图片描述
当我们访问success这个请求的时候,发现被重定向到了login页面要求进行身份认证,这正是 anyRequest和authenticated起了作用,我们在表单中输入用户user和密码(控制台随机生成的)填写后 就访问成功了
在这里插入图片描述
这样就认证成功了,那么在很多的时候我们可能某些页面只能管理员才能访问,普通的用户是不能访问,那么就要在配置文件中配置
我们在配置中加入新的配置项

@Override
    protected void configure(HttpSecurity http) throws Exception {


        http
                .authorizeRequests()
                .antMatchers("/500").permitAll()
                .antMatchers("/404").permitAll()
                .antMatchers("/403").permitAll()
                .antMatchers("/admin/index").hasRole("ADMIN")//指定权限为ADMIN才能访问
                .antMatchers("/person").hasAnyRole("ADMIN","USER")//指定多个权限都能访问
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()//使用表单认证方式
                .loginProcessingUrl("/login")//配置默认登录入口
                .and()
                .csrf().disable();

    }

这配置中我们新加了一个url配置/admin/index 并且用了hasRole, hasRole指定了特定的角色权限才能访问该url,并且hasRole默认截取了前面部分的ROLE_ 所以只需要填写ADMIN即可,无需像这样ROLE_ADMIN这样,除了这样,我们还需要配置内存认证用户等配置
hasAnyRole是指定了多个角色都能访问这个url,角色已逗号隔开,这里不管是user或者admin账户登录后都能访问这个person页面

 /**
     * 自定义认证策略
     * @return
     */
    @Autowired
    public void  configGlobal(AuthenticationManagerBuilder  auth) throws Exception {
        String password = passwordEncoder().encode("123456");

        logger.info("加密后的密码:" + password);

//        auth.inMemoryAuthentication().withUser("admin").password(password)
//                .roles("ADMIN").and();
        auth.inMemoryAuthentication().withUser("user").password(password)
                .roles("USER").and();

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

我们需要实现configGlobal 安全策略认证方法,AuthenticationManagerBuilder用于通过允许AuthenticationProvider容易地添加来建立认证机制。以下定义了内置认证与内置的“用户”和“管理”登录,这里不深入去了解。
这里使用了对登录的密码进行加密处理用到了spring security最强大的 加密器 BCryptPasswordEncoder 随机生成盐(用户的密码和盐混合在一起),每次生成的密码盐都不一样,这也是spring security BCryptPasswordEncoder强大之处。我们对密码进行了加密,使用了passwordEncoder中的encode进行了密码的加密,这里我强调一次passwordEncoder有2个方法一个用于加密,一个用于解密,其中passwordEncoder.encode是用来加密的,passwordEncoder.matches是用来解密的。

//auth.inMemoryAuthentication().withUser("admin").password(password).roles("ADMIN").and();
auth.inMemoryAuthentication().withUser("user").password(password).roles("USER").and();

这里添加了2个不同角色权限的内存用户,注意一个内存中只能同时有一个用户,很多教程都同时添加了多个用户

        auth.inMemoryAuthentication().withUser("user").password(password)
        .roles("USER").and().withUser("admin").roles("ADMIN").password(password);

不推荐这样的配置,这种配置系统运行时候内存同时有2个用户,这样是会出问题的,推荐添加内存用户的时候一次添加一个,多个的时候只留一个其它的都注释掉。
这样的话我们就能使用内存的用户进行权限的测试认证了
当我们运行项目,控制台就会打印加密后的密码

在这里插入图片描述
注意的是这里的这个密码代表的是123456,我们只需要在页面上输入123456就行,千万别把这串加密的串复制到表单密码框里
我们用这个user用户 去访问 admin/index页面看看会发生什么情况
在这里插入图片描述
此时会到我自定义的403页面,并且状态码是403,没有权限访问这个页面,那么我们用admin账号去访问看看有什么效果
在这里插入图片描述

admin账户登录,成功的进入了管理员权限的页面。

自定义表单登录页面

如何自定义表单登录?我们不可能用官方那个页面,那个页面属实不好看,所以我们自定义登录页面,我写了个简单的html的登录页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>标准登录页</title>
</head>
<body>
<h2>标准登录页</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr>
            <td>用户:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><button type="submit">登录</button></td>
        </tr>
    </table>
</form>
</body>
</html>

页面写好后就在controller配置访问url就行了,下面我们在security配置

 @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                .antMatchers("/500").permitAll()
                .antMatchers("/404").permitAll()
                .antMatchers("/403").permitAll()
                .antMatchers("/user/login").permitAll()//自定义登录页面的url
                .antMatchers("/admin/index").hasRole("ADMIN")//指定权限为ADMIN才能访问
                .antMatchers("/person").hasAnyRole("ADMIN","USER")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()//使用表单认证方式
                .loginProcessingUrl("/authentication/form")//配置默认登录入口
                .and()
                .csrf().disable();

    }

需要注意的是loginProcessingUrl的url是登录页面中form action 的地址,这样设置好后我们重启服务,访问/user/login就能看见自己自定义的登录页面并且也能登录了。
在这里插入图片描述
这样就可以通过写css样式来美化自定义的表单登录页,特别的方便。

如何处理登录失败、成功

如果用户登录失败该怎么给用户提示信息,登录成功了又给什么信息,其实非常的简单,只需要继承2个类就行,一个是成功结果处理器,一个是失败结果处理器
这个就是成功处理器,这里我没有做任何逻辑处理,如果登录成功了那么会进入到这个方法,并且会在控制台输出登录成功的日志。

@Component("myLoginSuccessHandler")
public class MyLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        response.setContentType("application/json;charset=UTF-8");
        
        logger.info("登录成功");
        //这里写你登录成功后的逻辑
    }
}

下面是登录失败的处理器
这个就是失败处理器,这里我没有做任何逻辑处理,如果登录失败了那么会进入到这个方法,并且会在控制台输出登录失败的日志。

@Component("myLoginFailureHandler")
public class MyLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");

        logger.info("登录失败");
        //这里写你登录失败的逻辑

    }
}

那么写好了这两个类,我们就得在security中进行配置,最后的配置如下

 @Override
    protected void configure(HttpSecurity http) throws Exception {


        http
                .authorizeRequests()
                .antMatchers("/500").permitAll()
                .antMatchers("/404").permitAll()
                .antMatchers("/403").permitAll()
                .antMatchers("/user/login").permitAll()
                .antMatchers("/admin/index").hasRole("ADMIN")//指定权限为ADMIN才能访问
                .antMatchers("/person").hasAnyRole("ADMIN","USER")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()//使用表单认证方式
                .loginProcessingUrl("/authentication/form")//配置默认登录入口
                .successHandler(myLoginSuccessHandler)//使用自定义的成功结果处理器
                .failureHandler(myLoginFailureHandler)//使用自定义失败的结果处理器
                .and()
                .csrf().disable();

    }

    /**
     * 自定义认证策略
     *
     * @return
     */
    @Autowired
    public void configGlobal(AuthenticationManagerBuilder auth) throws Exception {
        String password = passwordEncoder().encode("123456");

        logger.info("加密后的密码:" + password);

        auth.inMemoryAuthentication().withUser("admin").password(password)
                .roles("ADMIN").and();

//        auth.inMemoryAuthentication().withUser("user").password(password)
//                .roles("USER").and();

    }

    @Autowired
    private AuthenticationSuccessHandler myLoginSuccessHandler; //认证成功结果处理器

    @Autowired
    private AuthenticationFailureHandler myLoginFailureHandler; //认证失败结果处理器

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

注意这里注入成功、失败处理器一个是AuthenticationSuccessHandler,一个是AuthenticationFailureHandler,并且名字要和@Component的名字 一致
那么我们来进行测试,输入一个错误的密码或者不存在的账户,会发生什么错误啊,那么我们来进行测试
在这里插入图片描述
可以看到这里登录失败了,进入了失败结果处理器
我们来用一个正确的密码用户测试一下

在这里插入图片描述
可以看到这里登录成功,进入了成功结果处理器,就能访问相关的服务了。那么讲完了基本的使用和认证的处理,那么我们来讲一下注解权限@PreAuthorize() 这个注解设置了就可以不用在security中配置.hasRole,hasAnyRole
列如如下配置

    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    @GetMapping(value = "/admin/index")
    public String adminPage(){
        return "admin/admin";
    }

这样设置了一样可以进行身份认证,但是如果security中配置了同一个url,那么security中配置要优先于使用@PreAuthorize配置的,这两种方式都能实现身份认证,看自己怎么选择。
@PreAuthorize的其它参数
hasRole (当前用户是否拥有指定角色)
hasAnyRole(多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true)
hasAuthority(和hasRole一样的效果)
hasAnyAuthority(和hasAnyRole一样的效果)
hasPermission(不常用)里面有2个参数的,有三个参数的如下
@PreAuthorize(“hasPermission(‘user’, ‘ROLE_USER’)”)
@PreAuthorize(“hasPermission(‘targetId’,‘targetType’,‘permission’)”)

安全框架的原理

本文的最后给大家讲一下SpringSecurity的基本原理,便于加深对这个安全框架的理解,日后便于学习Shiro框架做对比
在这里插入图片描述
SpringSecurity其实是一组filter,所有访问的请求都会经过spring security的过滤器,然后返回、
其中图片上最核心的是绿色的过滤器,是用来认证用户的身份,每一块代表一个过滤器,UsernamePasswordAuthenticationFilter是用来处理表单登录认证的,而BasicAuthenticationFilter是用来处理HttpBasic认证登录的。
这些绿色的过滤器主要是工作是检查你当前的请求是否有过滤器需要的信息,比如UsernamePasswordAuthenticationFilter这个过滤器,如果你当前的请求是登录的请求,并且带了用户名和密码,那么就会进这个过滤器进行处理,如果当前的请求是登录请求但是没有带用户名和密码,那么就会放过去给下一个过滤器,比如下一个过滤器是BasicAuthenticationFilter,那么它就会检查当前的请求头有没有Basic开头的信息,如果有的话会尝试拿出来做Basic64解码,然后取出用户名和密码尝试去登录,在SpringSecurity中还有很多的过滤器,它会尝试一个一个的往下走,任何一个过滤器成功的完成了用户认证,它会做一个标记,经过这些绿色的过滤器之后,最终会到一个橙色的过滤(FilterSecurityInterceptor),这个过滤器是SpringSecurity过滤器链最后一环,它是最后的守门人,在它身后就是我们自己写的controller rest 服务了,这个过滤器决定了你能不能访问后面的rest 服务了,那么它依据什么来判断呢?它是依据我们security中的配置来判断的,如果通过了就能访问服务,没有通过就抛出不同的异常,比如只有vip用户能够访问页面,用普通用户虽然认证成功了,但是你没有vip用户的权限,那么就会抛出权限的异常。在异常抛出之后,在FilterSecurityInterceptor的前面还有一个ExceptionTranslationFilter过滤器,这个过滤器就是用来捕获FilterSecurityInterceptor抛出的异常,这个过滤器会根据抛出的异常来进行处理器。
这里注意的图片上绿色过滤器我们是可以通过配置来觉得是否生效,其它颜色的过滤器我们都是不能控制的,并且也不能交换位置。

最后希望大家越来越牛逼,早日成为大神。
本文demo的地址:https://github.com/zhoubiao188/spring-security-auth

  • 65
    点赞
  • 249
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值