当前位置: 移动技术网 > IT编程>开发语言>Java > 如何使用SpringSecurity保护程序安全

如何使用SpringSecurity保护程序安全

2019年09月09日  | 移动技术网IT编程  | 我要评论

首先,引入依赖:

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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网