Spring Boot(六):集成Spring Security

前言:

百度百科中是这样解释Spring Security的:
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

对于我的理解:
Spring Security可以集中一个权限控制系统,可以用来保护 Web 应用的安全;
核心功能是:

  • 认证(你是谁)
  • 授权(你能干什么)
  • 攻击防护(防止伪造身份)

集成Spring Security步骤:

一、maven中添加依赖

<!--spring-boot-security安全框架SpringSecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

这个是最首要,也是最关键的;配置好后,直接运行项目,打开浏览器,访问项目上的接口时,你会发现,访问不了,直接进入了一个登入页面,如下图:

图片.png

原因:因为在SpringBoot中,默认的Spring Security就是生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。
Spring Security提供了一个默认的用户,用户名是user,而密码则是启动项目的时候自动生成的。
我们查看项目启动的日志,会发现如下的一段Log:
图片.png

每个人看到的password肯定是不一样的,我们直接用user和启动日志中的密码进行登录。
登录成功后,就跳转到了接口正常调用的页面了。
如果你不想一开始就使能Spring Security,怎么办呢;
之前有人告诉我,在application配置中直接加:security.basic.enabled=false 即可;
不行!不行!不行!是真的不行呀,根本没有关闭Spring Security,查了下资料:
只有在spring boot1.5配置security关闭http基本验证,可以在application配置中增加;security.basic.enabled=false进行关闭,但是在spring boot 2.0+之后这样配置就不能生效了,下面的配置文件讲解中会降到怎么进行关闭,^- ^;

二、对SpringSecurity进行相应的配置

新建配置类:SecurityConfig,继承WebSecurityConfigurerAdapter类,然后重写父类中的configure(HttpSecurity http) 方法。如下

/**
 * @author AxeLai
 * @date 2019-04-30 15:15
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置需要登入验证
        http.formLogin()          // 定义当需要用户登录时候,转到的登录页面。
                .and()
                .authorizeRequests()    // 定义哪些URL需要被保护、哪些不需要被保护
                .anyRequest()        // 任何请求,登录后可以访问
                .authenticated();

        //配置不需要登入验证
//        http.authorizeRequests()
//                .anyRequest()
//                .permitAll()
//                .and()
//                .logout()
//                .permitAll();
    }
}

上面代码中,注释‘配置需要登入验证’,打开‘配置不需要登入验证’就解决了security的关闭问题^- ^.

如果你只是为了测试Security的安全验证功能,上面这些就足够了;但是,如果需要进行登入的用户身份认证,权限设置等,还就需要接着往下看了:

三、配置用户认证逻辑

因为我们是要有自己的一套用户体系的,所以要配置用户认证:
新建LoginFilter配置类,继承UserDetailsService类:

package com.example.demo.loginfilter;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * @author AxeLai
 * @date 2019-05-01 14:01
 */
@Component
public class LoginFilter  implements UserDetailsService {
    private Logger logger = LoggerFactory.getLogger("adminLogger");

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("用户的用户名: {}", username);
        // TODO 根据用户名,查找到对应的密码,与权限
        // 封装用户信息,并返回。参数分别是:用户名,密码,用户权限
        User user = new User(username, "123456",
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        return user;
    }
}

这里我们没有进行过多的校验,用户名可以随意的填写,但是密码必须得是“123456”,这样才能登录成功。
同时可以看到,这里User对象的第三个参数,它表示的是当前用户的权限,我们将它设置为”admin”。
运行一下程序进行测试


图片.png

输入随意的用户和密码123456,发现根本登入不了;
后台报错:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"


图片.png

下面说下原因:
这是因为Spring boot 2.0.3引用的security 依赖是 spring security 5.X版本,此版本需要提供一个PasswordEncorder的实例,否则后台汇报错误:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
并且页面毫无响应。

更改:
把SecurityConfig改为:

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* @author AxeLai
* @date 2019-04-30 15:15
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Bean
   public PasswordEncoder passwordEncoder(){
       //当然,如果你有自己的加密方法,这个方法就写自己的加密方法好了
       return new BCryptPasswordEncoder();
   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       //配置需要登入验证
       http.formLogin()          // 定义当需要用户登录时候,转到的登录页面。
               .and()
               .authorizeRequests()    // 定义哪些URL需要被保护、哪些不需要被保护
               .anyRequest()        // 任何请求,登录后可以访问
               .authenticated();

       //配置不需要登入验证
//        http.authorizeRequests()
//                .anyRequest()
//                .permitAll()
//                .and()
//                .logout()
//                .permitAll();
   }
}

把LoginFilter改为:

package com.example.demo.loginfilter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * @author AxeLai
 * @date 2019-05-01 14:01
 */
@Component
public class LoginFilter  implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    private Logger logger = LoggerFactory.getLogger("adminLogger");

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("用户的用户名: {}", username);
        // TODO 根据用户名,查找到对应的密码,与权限
        // 封装用户信息,并返回。参数分别是:用户名,密码,用户权限
        User user = new User(username, passwordEncoder.encode("123456"),
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        return user;
    }
}

重启项目,随便的用户名+密码123456登入,你就发现登入已经成功了 ,直接进入我们项目的首页,后台会获取到你的用户名:


图片.png

讲解:
1.当我们写LoginFilter 类时;里面实现了一个方法loadUserByUsername,并返回了一个UserDetails。这个UserDetails 就是封装了用户信息的对象,里面包含了七个方法:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.security.core.userdetails;

import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

我们在返回UserDetails的实现类User的时候,可以通过User的构造方法,设置对应的参数就可以;
2.SpringSecurity中有一个PasswordEncoder接口:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.security.crypto.password;

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

我们只需要自己实现这个接口,并在配置文件中配置一下就可以了。
上面我只是暂时以默认提供的一个实现类进行测试:

    @Bean
    public PasswordEncoder passwordEncoder(){
        //当然,如果你有自己的加密方法,这个方法就写自己的加密方法好了
        return new BCryptPasswordEncoder();
    }

四、个性化用户认证逻辑

在上面的测试中,一直都是使用的默认的登录界面,我相信每个产品都是有自己的登录界面设计的,所以我们这一节了解一下如何自定义登录页面。
我们先写一个简单的登录页面login.html:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<h2>自定义登录页面</h2>
<form action="/user/login" 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>

完成了登录页面之后,就需要将它配置进行SecurityConfig进行更改:

    //配置需要登入验证的自定义配置
        http.formLogin()          // 定义当需要用户登录时候,转到的登录页面。
                .loginPage("/login.html")      // 设置登录页面
                .loginProcessingUrl("/user/login") // 自定义的登录接口
                .and()
                .authorizeRequests()    // 定义哪些URL需要被保护、哪些不需要被保护
                .antMatchers("/login.html").permitAll()   // 设置所有人都可以访问登录页面
                .anyRequest()        // 任何请求,登录后可以访问
                .authenticated()
                .and()
                .csrf().disable();     // 关闭csrf防护

这样,每当我们访问被保护的接口的时候,就会调转到login.html页面,如下:

图片.png

最后附上项目结构图:
图片.png

代码已上传到:https://github.com/AxeLai/SpringBoot-.git
OVER IS NOT OVER!


番外

上面这些,是在前后端不分离的情况下进行开发编写的,现如今一般项目都前后端分离了,后端提供接口供前端调用,返回JSON格式的数据给前端。刚才那样,调用了被保护的接口,直接进行了页面的跳转,这样执行不太友好。
处理:在前后端分离的情况下,我们登录成功了直接向前端返回用户的个人信息,而不是直接进行跳转。登录失败也是同样的道理。
这里涉及到了Spring Security中的两个接口AuthenticationSuccessHandler和AuthenticationFailureHandler。我们可以实现这个接口,并进行相应的配置就可以了。 当然框架是有默认的实现类的,我们可以继承这个实现类再来自定义自己的业务:

@Component("myAuthenctiationSuccessHandler")
public class MyAuthenctiationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
  private Logger logger = LoggerFactory.getLogger(getClass());
  @Autowired
  private ObjectMapper objectMapper;
  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException {
    logger.info("登录成功");
    response.setContentType("application/json;charset=UTF-8");
    response.getWriter().write(objectMapper.writeValueAsString(authentication));
  }
}

这里我们通过response返回一个JSON字符串回去。
这个方法中的第三个参数Authentication,它里面包含了登录后的用户信息(UserDetails),Session的信息,登录信息等。

@Component("myAuthenctiationFailureHandler")
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
 
  private Logger logger = LoggerFactory.getLogger(getClass());
 
  @Autowired
  private ObjectMapper objectMapper;
 
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                    AuthenticationException exception) throws IOException, ServletException {
 
    logger.info("登录失败");
 
    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    response.setContentType("application/json;charset=UTF-8");
    response.getWriter().write(objectMapper.writeValueAsString(new BaseResponse(exception.getMessage())));
  }
}

这个方法中的第三个参数AuthenticationException,包括了登录失败的信息。
同样的,还是需要在配置文件中进行配置,这里就不贴出全部的代码了,只贴出相应的语句

.successHandler(myAuthenticationSuccessHandler) // 自定义登录成功处理 
.failureHandler(myAuthenticationFailureHandler) // 自定义登录失败处理

OVER IS OVER!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容