如何使用SpringSecurity保护程序安全

首先,引入依赖:

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

引入此依赖之后,你的web程序将拥有以下功能:

  • 所有请求路径都需要认证
  • 不需要特定的角色和权限
  • 没有登录页面,使用HTTP基本身份认证
  • 只有一个用户,名称为user

配置SpringSecurity

springsecurity配置项,最好保存在一个单独的配置类中:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

配置用户认证方式

首先,要解决的就是用户注册,保存用户的信息。springsecurity提供四种存储用户的方式:

  • 基于内存(生产肯定不使用)
  • 基于JDBC
  • 基于LDAP
  • 用户自定义(最常用)

使用其中任意一种方式,需要覆盖configure(AuthenticationManagerBuilder auth)方法:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  }
}

1.基于内存

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.inMemoryAuthentication()
      .withUser("zhangsan").password("123").authorities("ROLE_USER")
      .and()
      .withUser("lisi").password("456").authorities("ROLE_USER");
}

2.基于JDBC

@Autowired
DataSource dataSource;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.jdbcAuthentication()
    .dataSource(dataSource);
}

基于JDBC的方式,你必须有一些特定表表,而且字段满足其查询规则:

public static final String DEF_USERS_BY_USERNAME_QUERY =
  "select username,password,enabled " +
  "from users " +
  "where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
  "select username,authority " +
  "from authorities " +
  "where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
  "select g.id, g.group_name, ga.authority " +
  "from groups g, group_members gm, group_authorities ga " +
  "where gm.username = ? " +
  "and g.id = ga.group_id " +
  "and g.id = gm.group_id";

当然,你可以对这些语句进行一下修改:

@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=?");

这有一个问题,你数据库中的密码可能是一种加密方式加密过的,而用户传递的是明文,比较的时候需要进行加密处理,springsecurity也提供了相应的功能:

@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(new StandardPasswordEncoder("53cr3t");

passwordEncoder方法传递的是PasswordEncoder接口的实现,其默认提供了一些实现,如果都不满足,你可以实现这个接口:

  • BCryptPasswordEncoder
  • NoOpPasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder
  • StandardPasswordEncoder(SHA-256)

3.基于LDAP

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.ldapAuthentication()
    .userSearchBase("ou=people")
    .userSearchFilter("(uid={0})")
    .groupSearchBase("ou=groups")
    .groupSearchFilter("member={0}")
    .passwordCompare()
    .passwordEncoder(new BCryptPasswordEncoder())
    .passwordAttribute("passcode")
    .contextSource()
      .root("dc=tacocloud,dc=com")
      .ldif("classpath:users.ldif");

4.用户自定义方式(最常用)

首先,你需要一个用户实体类,它实现UserDetails接口,实现这个接口的目的是为框架提供更多的信息,你可以把它看作框架使用的实体类:

@Data
public class User implements UserDetails {

  private Long id;
  private String username;
  private String password;
  private String fullname;
  private String city;
  private String phoneNumber;

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return null;
  }

  @Override
  public boolean isAccountNonExpired() {
    return false;
  }

  @Override
  public boolean isAccountNonLocked() {
    return false;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return false;
  }

  @Override
  public boolean isEnabled() {
    return false;
  }
}

有了实体类,你还需要Service逻辑层,springsecurity提供了UserDetailsService接口,见名知意,你只要通过loadUserByUsername返回一个UserDetails对象就成,无论是基于文件、基于数据库、还是基于LDAP,剩下的对比判断交个框架完成:

@Service
public class UserService implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    return null;
  }

}

最后,进行应用:

@Autowired
private UserDetailsService userDetailsService;

@Bean
public PasswordEncoder encoder() {
  return new StandardPasswordEncoder("53cr3t");
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(userDetailsService)
    .passwordEncoder(encoder());
}

配置认证路径

知道了如何认证,但现在有几个问题,比如,用户登录页面就不需要认证,可以用configure(HttpSecurity http)对认证路径进行配置:

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

你可以通过这个方法,实现以下功能:

  • 在提供接口服务前,判断请求必须满足某些条件
  • 配置登录页面
  • 允许用户注销登录
  • 跨站点伪造请求防护

1.保护请求

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
    .antMatchers("/design", "/orders").hasRole("ROLE_USER")
    .antMatchers(“/”, "/**").permitAll();
}

要注意其顺序,除了hasRole和permitAll还有其它访问认证方法:

方法 作用
access(String) 如果给定的SpEL表达式的计算结果为true,则允许访问
anonymous() 允许访问匿名用户
authenticated() 允许访问经过身份验证的用户
denyAll() 无条件拒绝访问
fullyAuthenticated() 如果用户完全通过身份验证,则允许访问
hasAnyAuthority(String...) 如果用户具有任何给定权限,则允许访问
hasAnyRole(String...) 如果用户具有任何给定角色,则允许访问
hasAuthority(String) 如果用户具有给定权限,则允许访问
hasIpAddress(String) 如果请求来自给定的IP地址,则允许访问
hasRole(String) 如果用户具有给定角色,则允许访问
not() 否定任何其他访问方法的影响
permitAll() 允许无条件访问
rememberMe() 允许通过remember-me进行身份验证的用户访问

大部分方法是为特定方式准备的,但是access(String)可以使用SpEL进一些特殊的设置,但其中很大一部分也和上面的方法相同:

表达式 作用
authentication 用户的身份验证对象
denyAll 始终评估为false
hasAnyRole(list of roles) 如果用户具有任何给定角色,则为true
hasRole(role) 如果用户具有给定角色,则为true
hasIpAddress(IP address) 如果请求来自给定的IP地址,则为true
isAnonymous() 如果用户是匿名用户,则为true
isAuthenticated() 如果用户已通过身份验证,则为true
isFullyAuthenticated() 如果用户已完全通过身份验证,则为true(未通过remember-me进行身份验证)
isRememberMe() 如果用户通过remember-me进行身份验证,则为true
permitAll 始终评估为true
principal 用户的主要对象

示例:

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
    .antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
    .antMatchers(“/”, "/**").access("permitAll");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
    .antMatchers("/design", "/orders").access("hasRole('ROLE_USER') && " +
     "T(java.util.Calendar).getInstance().get("+"T(java.util.Calendar).DAY_OF_WEEK) == " +     "T(java.util.Calendar).TUESDAY")
    .antMatchers(“/”, "/**").access("permitAll");
}

2.配置登录页面

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
    .antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
    .antMatchers(“/”, "/**").access("permitAll")
    .and()
    .formLogin()
      .loginPage("/login");
}

// 增加视图处理器
@Overridepublic void addViewControllers(ViewControllerRegistry registry) {
  registry.addViewController("/").setViewName("home");
  registry.addViewController("/login");
}

默认情况下,希望传递的是username和password,当然你可以修改:

.and()
  .formLogin()
    .loginPage("/login")
    .loginProcessingUrl("/authenticate")
    .usernameParameter("user")
    .passwordParameter("pwd")

也可修改默认登录成功的页面:

.and()
  .formLogin()
    .loginPage("/login")
    .defaultSuccessUrl("/design")

3.配置登出

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

4.csrf攻击

springsecurity默认开启了防止csrf攻击,你只需要在传递的时候加上:

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

当然,你也可以关闭,但是不建议这样做:

.and()
  .csrf()
    .disable()

知道用户是谁

仅仅控制用户登录有时候是不够的,你可能还想在程序的其它地方获取已经登录的用户信息,有几种方式可以做到:

  • 将Principal对象注入控制器方法
  • 将Authentication对象注入控制器方法
  • 使用SecurityContextHolder获取安全上下文
  • 使用@AuthenticationPrincipal注解方法

1.将Principal对象注入控制器方法

@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus,Principal principal) {
  ...
  User user = userRepository.findByUsername(principal.getName());
  order.setUser(user);
  ...
}

2.将Authentication对象注入控制器方法

@PostMappingpublic String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, Authentication authentication) {
  ...
  User user = (User) authentication.getPrincipal();
  order.setUser(user);
  ...
}

3.使用SecurityContextHolder获取安全上下文

Authentication authentication =
  SecurityContextHolder.getContext().getAuthentication();
  User user = (User) authentication.getPrincipal();

4.使用@AuthenticationPrincipal注解方法

@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus, @AuthenticationPrincipal User user) {
  if (errors.hasErrors()) {
    return "orderForm";
  }
  order.setUser(user);
  orderRepo.save(order);
  sessionStatus.setComplete();
  return "redirect:/";
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot安全认证Security的实现方法

    一.基本环境搭建 父pom依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> </parent> 1. 添加pom依赖: <dependency> <groupId&

  • SpringBoot2.0 整合 SpringSecurity 框架实现用户权限安全管理方法

    一.Security简介 1.基础概念 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring的IOC,DI,AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为安全控制编写大量重复代码的工作. 2.核心API解读 1).SecurityContextHolder 最基本的对象,保存着当前会话用户认证,权限,鉴权等核心数据.Secu

  • 详解spring security安全防护

    前言 xss攻击(跨站脚本攻击):攻击者在页面里插入恶意脚本代码,用户浏览该页面时,脚本代码就会执行,达到攻击者的目的.原理就是:攻击者对含有漏洞的服务器注入恶意代码,引诱用户浏览受到攻击的服务器,并打开相关页面,执行恶意代码. xss攻击方式:一.反射性攻击,脚本代码作为url参数提交给服务器,服务器解析执行后,将脚本代码返回给浏览器,最后浏览器解析执行攻击代码:二.存储性攻击,和发射性攻击的区别是,脚本代码存储在服务器,下次在请求时,不用再提交脚本代码.其中一个示例图如下所示: CSRF攻击

  • Spring Boot如何使用Spring Security进行安全控制

    我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache Shiro.spring Security). 本文将具体介绍在Spring Boot中如何使用Spring Security进行安全控制. 准备工作 首先,构建一个简单的Web工程,以用于后续添加安全控制,也可以用之前Chapter3-1-2做为基础工程.若对如何使用Spring Boot构建We

  • 如何使用SpringSecurity保护程序安全

    首先,引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 引入此依赖之后,你的web程序将拥有以下功能: 所有请求路径都需要认证 不需要特定的角色和权限 没有登录页面,使用HTTP基本身份认证 只有一个用户,名称为user 配置

  • 用vbs记录屏幕保护程序的开始时间和结束时间

    问: 您好,脚本专家!如何记录屏幕保护程序的开始时间和结束时间? -- JS 答: 您好,JS.您知道,一位脚本专家(嘿,谁说"肯定是 Greg"?)年纪大得记得屏幕保护程序刚出现的日子.那时,这类脚本毫无意义.毕竟,屏幕保护程序启动后,每个人都神魂颠倒,从未想过让它结束.事实上,作为计算机支持人员的这位脚本专家首先必须做的一件事就是在每个人的桌面上创建快捷方式,使他们能够随时启动"飞转的小烤炉". 那时人们很容易得到快乐. 啊,但是活在过去没有意义,对吧?在今天的

  • 用vbs实现配置无人登录计算机时使用的屏幕保护程序

    问: 您好,脚本专家!我最近下载了你们的"脚本中心"屏幕保护程序,当有人登录计算机时,它运行起来棒极了.但是无人登录时,计算机却使用其它屏幕保护程序.如何让计算机在无人登录时也使用"脚本中心"屏幕保护程序? -- RF 答: 您好,RF.您知道,我们遇到过这种情况,您可能在设法欺骗这些老脚本专家们.举个例子来说,我们怎么知道您希望无人登录计算机时运行的屏幕保护程序就是我们的屏幕保护程序?也许您只是奉承脚本专家,好让脚本专家回答您的问题.回答完问题后,您就会把我们甩掉

  • c#制作屏幕保护程序步骤(字幕屏保)

    屏幕保护程序的扩展名虽然是"scr",但其实是一个可执行的"exe"文件.但他又是一个比较独特的"exe"文件.下面就来探讨一下,用C#是如何编写屏幕保护的整个过程. 二.C#编写字幕显示屏保程序的关键步骤以及解决方法:(1)设定程序的窗体符合屏幕保护的要求:由于屏幕保护程序就是一个可执行程序,所以在编写屏幕保护程序的时候,首先按照可执行程序来设计.但屏幕保护有自身的特点.譬如:屏幕保护都是充满整个屏幕的,并且没有无边.屏幕保护运行的时候,不能显

  • 巧妙卸载公用电脑的屏幕保护程序

    学校的微机室是多个班级共用,一个班级的学生上课时指定了屏幕保护程序并设定了口令,下一个班级的学生上课时,如果在指定的时间内(最短时间为1分钟)没有使用计算机,即运行屏幕保护程序.由于这个班的学生不知道口令,所以只好按下Reset键重启计算机.此时计算机的硬盘正高速旋转,按Reset键重启计算机时会划伤存有大量数据的硬盘.长此一往,就会使硬盘夭折,不仅会丢失大量教学数据,而且还会增加学校的经济负担.因此,在学校多班并用的微机室,卸载计算机的屏幕保护程序已成为大多数教师的迫切要求.下面,笔者把自己卸

  • 卸载掉您计算机中的屏幕保护程序

    学校的微机室是多个班级共用,一个班级的学生上课时指定了屏幕保护程序并设定了口令,下一个班级的学生上课时,如果在指定的时间内(最短时间为1分钟)没有使用计算机,即运行屏幕保护程序.由于这个班的学生不知道口令,所以只好按下Reset键重启计算机.此时计算机的硬盘正高速旋转,按Reset键重启计算机时会划伤存有大量数据的硬盘.长此一往,就会使硬盘夭折,不仅会丢失大量教学数据,而且还会增加学校的经济负担.因此,在学校多班并用的微机室,卸载计算机的屏幕保护程序已成为大多数教师的迫切要求.下面,笔者把自己卸

  • VB键盘鼠标无动作调用程序的尝试

    我想要实现的功能是,当键盘无输入.鼠标无移动或点击动作时调用程序.首先想到的是用钩子HOOK来获取键盘或者鼠标的动作,如果无动作时调用程序.我尝试的结果是HOOK来HOOK去总是有问题. 后来想到Windows的屏幕保护程序就是当键盘鼠标无动作时进入屏幕保护的,于是改变思路,想把程序做成这样的形式,键盘鼠标无动作,系统进入屏幕保护,然后检测系统是否运行屏幕保护程序,如果运行的话则调用程序.这种方式就是以屏幕保护程序作为中介,把检测键盘鼠标动作的工作交给屏幕保护程序来完成了.SystemParam

  • 用vbscript实现修改屏幕保护的等待时间长度

    问: 嗨,Scripting Guy!是否可以使用脚本来修改计算机上屏幕保护的等待时间长度? -- JN 答: 嗨,JN.出于某些原因,Microsoft 的脚本技术在涉及 Windows 设置和组件方面有些不足,例如屏幕保护.墙纸.任务栏和开始菜单等等.您可以使用 WMI(尤其是 Win32_Desktop 类)来读取这些值,但不能使用 Win32_Desktop 类(或是任何等价的类或对象)来修改这些值.为什么呢?老实说,我们也不知道: 幸好,这些值大都存储在 Windows 注册表中,而只

  • 做一个优秀程序员应该知道的15件事

    1. 懂得分享.尽可能使用开源,并且当你有能力的时候,要对其有所贡献.聚全社会之智慧,胜过某些"大"公司之短视. 2. 公平竞争.尝试其他技术.框架.方法和观点.不要总以为只有你的选择才是可行的.别的选择也有可能比你的要强得多.要以开放的心态,来检验其他人的选择. 3. 不要攻击他人.像第2条所说的,不要仅仅因为别人恰巧使用.Net.Java或PHP就去攻击他们(我在这方面有一次教训).有时,它们或许要比你所认为的更有效.只要别人不是一无是处,你就可以从他们那里学到很多东西. 4. 自

  • 利用注册表防止学生修改屏幕保护密码

    作为电脑教师,最烦的就是学生随手修改了屏幕保护的密码.有没有好办法去防止学生修改屏保密码呢?我们可利用注册表做到这一点. 1.隐藏控制面板中的"显示"设置 在"开始→运行"中键入"regedit",确定后打开注册表编辑器.定位到HKEY_CURR ENT_USER\Software\Mic-rosoft\Windows\CurrentVersion\Policies\System,新建名为"NoDispcpl"的子键,设其值为

随机推荐