软件编程
位置:首页>> 软件编程>> java编程>> 如何使用SpringSecurity保护程序安全

如何使用SpringSecurity保护程序安全

作者:_元夕  发布时间:2022-09-08 19:57:50 

标签:Spring,Security,程序安全

首先,引入依赖:


<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:/";
}

来源:https://www.cnblogs.com/NameZZH/p/11457146.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com