当前位置: 首页 > 编程笔记 >

如何使用SpringSecurity保护程序安全

苏畅
2023-03-14
本文向大家介绍如何使用SpringSecurity保护程序安全,包括了如何使用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:/";
}

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

 类似资料:
  • 我们有一个应用程序,它使用Azure Spring Boot Active Directory starter“com.microsoft.Azure:Azure Active Directory Spring Boot starter”和Spring Security来保护对该应用程序的访问。这一切都很好。 这是按照以下说明完成的: https://docs.microsoft.com/en-u

  • 问题内容: 我想使用PayPal出售Java应用程序。收到付款后,会向客户发送一次性下载链接到我的应用程序。 我的问题是,如何防止人们将.jar文件发送给他们的朋友/将其上传到互联网上? 显然,我需要在应用程序中进行某种检查,该检查仅允许它在一台计算机上运行。那是另一个问题,我不希望客户对一台计算机有限制,他们应该能够在家中和工作中运行它。 也许某种CD键可以解决问题?那里有Java的CD密钥资源

  • 屏幕保护程序     设定输出影像时,若不执行任何操作,需经过多久才会自动启动屏幕保护程序。只要按下PSP™主机的任何一个按钮,即能解除屏幕保护程序。 关 不启动屏幕保护程序。 5分后 5分后启动屏幕保护程序。 10分后 10分后启动屏幕保护程序。 15分后 15分后启动屏幕保护程序。 提示 执行以下操作时,屏幕保护程序可能会暂时停用。 播放UMD™ Video或暂停播放时 播放保存于Memory

  • 使用 HTTPS 保护站点安全 构建 PWA 应用时,HTTPS 是必不可少的条件之一。使用 HTTP 协议的应用存在着一定的安全隐患,这是因为 HTTP 本身不具备加密的功能,通信中使用明文传输请求和响应的内容,内容可能会被窃听,而且 HTTP 缺少对通信双方身份进行校验的环节,也无法证明报文内容的完整性,存在身份伪装和信息被篡改的风险。所以,我们应该严格地使用 HTTPS 协议来保护 PWA

  • 问题内容: 我是软件开发的初学者,我想知道如何在Java应用程序中实施保护系统,以防止盗版。我知道没有完美的解决方案。但是我只想知道如何保护它。而且不要告诉我将其开源,就我而言这是不可能的:)。 感谢您的时间和答复。 问题答案: 由于可以轻松地破解任何许可证执行解决方案,因此我建议您完全不要尝试实现复制保护。许可证管理给您的用户带来不便。 如果确定要实施复制保护,则可以使用开源库。 此外,您应该混

  • 问题内容: 我知道有很多类似的问题,但是没有一个是用户可以访问代码的HTML / javascript应用程序。 我有一个用nodejs编写的私有REST API。它是私有的,因为它的唯一目的是为我的HTML5客户端应用程序(Chrome应用程序和Adobe Air应用程序)提供服务器。因此,API密钥不是一个好的解决方案,因为任何用户都可以看到javascript代码。 我想避免机器人在服务器上