如何使用SpringSecurity保护程序安全
首先,引入依赖:
org.springframework.boot spring-boot-starter-security
引入此依赖之后,你的web程序将拥有以下功能:
- 所有请求路径都需要认证
- 不需要特定的角色和权限
- 没有登录页面,使用HTTP基本身份认证
- 只有一个用户,名称为user
配置SpringSecurity
springsecurity配置项,最好保存在一个单独的配置类中:
@Configuration
@EnableWebSecurity
publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{
}
配置用户认证方式
首先,要解决的就是用户注册,保存用户的信息。springsecurity提供四种存储用户的方式:
- 基于内存(生产肯定不使用)
- 基于JDBC
- 基于LDAP
- 用户自定义(最常用)
使用其中任意一种方式,需要覆盖configure(AuthenticationManagerBuilderauth)方法:
@Configuration
@EnableWebSecurity
publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
}
}
1.基于内存
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123").authorities("ROLE_USER")
.and()
.withUser("lisi").password("456").authorities("ROLE_USER");
}
2.基于JDBC
@Autowired
DataSourcedataSource;
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
auth.jdbcAuthentication()
.dataSource(dataSource);
}
基于JDBC的方式,你必须有一些特定表表,而且字段满足其查询规则:
publicstaticfinalStringDEF_USERS_BY_USERNAME_QUERY= "selectusername,password,enabled"+ "fromusers"+ "whereusername=?"; publicstaticfinalStringDEF_AUTHORITIES_BY_USERNAME_QUERY= "selectusername,authority"+ "fromauthorities"+ "whereusername=?"; publicstaticfinalStringDEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY= "selectg.id,g.group_name,ga.authority"+ "fromgroupsg,group_membersgm,group_authoritiesga"+ "wheregm.username=?"+ "andg.id=ga.group_id"+ "andg.id=gm.group_id";
当然,你可以对这些语句进行一下修改:
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("selectusername,password,enabledfromUsers"+
"whereusername=?")
.authoritiesByUsernameQuery("selectusername,authorityfromUserAuthorities"+
"whereusername=?");
这有一个问题,你数据库中的密码可能是一种加密方式加密过的,而用户传递的是明文,比较的时候需要进行加密处理,springsecurity也提供了相应的功能:
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("selectusername,password,enabledfromUsers"+
"whereusername=?")
.authoritiesByUsernameQuery("selectusername,authorityfromUserAuthorities"+
"whereusername=?")
.passwordEncoder(newStandardPasswordEncoder("53cr3t");
passwordEncoder方法传递的是PasswordEncoder接口的实现,其默认提供了一些实现,如果都不满足,你可以实现这个接口:
- BCryptPasswordEncoder
- NoOpPasswordEncoder
- Pbkdf2PasswordEncoder
- SCryptPasswordEncoder
- StandardPasswordEncoder(SHA-256)
3.基于LDAP
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.passwordCompare()
.passwordEncoder(newBCryptPasswordEncoder())
.passwordAttribute("passcode")
.contextSource()
.root("dc=tacocloud,dc=com")
.ldif("classpath:users.ldif");
4.用户自定义方式(最常用)
首先,你需要一个用户实体类,它实现UserDetails接口,实现这个接口的目的是为框架提供更多的信息,你可以把它看作框架使用的实体类:
@Data
publicclassUserimplementsUserDetails{
privateLongid;
privateStringusername;
privateStringpassword;
privateStringfullname;
privateStringcity;
privateStringphoneNumber;
@Override
publicCollectiongetAuthorities(){
returnnull;
}
@Override
publicbooleanisAccountNonExpired(){
returnfalse;
}
@Override
publicbooleanisAccountNonLocked(){
returnfalse;
}
@Override
publicbooleanisCredentialsNonExpired(){
returnfalse;
}
@Override
publicbooleanisEnabled(){
returnfalse;
}
}
有了实体类,你还需要Service逻辑层,springsecurity提供了UserDetailsService接口,见名知意,你只要通过loadUserByUsername返回一个UserDetails对象就成,无论是基于文件、基于数据库、还是基于LDAP,剩下的对比判断交个框架完成:
@Service
publicclassUserServiceimplementsUserDetailsService{
@Override
publicUserDetailsloadUserByUsername(Strings)throwsUsernameNotFoundException{
returnnull;
}
}
最后,进行应用:
@Autowired
privateUserDetailsServiceuserDetailsService;
@Bean
publicPasswordEncoderencoder(){
returnnewStandardPasswordEncoder("53cr3t");
}
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
配置认证路径
知道了如何认证,但现在有几个问题,比如,用户登录页面就不需要认证,可以用configure(HttpSecurityhttp)对认证路径进行配置:
@Override
protectedvoidconfigure(HttpSecurityhttp)throwsException{
}
你可以通过这个方法,实现以下功能:
- 在提供接口服务前,判断请求必须满足某些条件
- 配置登录页面
- 允许用户注销登录
- 跨站点伪造请求防护
1.保护请求
@Override
protectedvoidconfigure(HttpSecurityhttp)throwsException{
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(listofroles) | 如果用户具有任何给定角色,则为true |
| hasRole(role) | 如果用户具有给定角色,则为true |
| hasIpAddress(IPaddress) | 如果请求来自给定的IP地址,则为true |
| isAnonymous() | 如果用户是匿名用户,则为true |
| isAuthenticated() | 如果用户已通过身份验证,则为true |
| isFullyAuthenticated() | 如果用户已完全通过身份验证,则为true(未通过remember-me进行身份验证) |
| isRememberMe() | 如果用户通过remember-me进行身份验证,则为true |
| permitAll | 始终评估为true |
| principal | 用户的主要对象 |
示例:
@Override
protectedvoidconfigure(HttpSecurityhttp)throwsException{
http.authorizeRequests()
.antMatchers("/design","/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”,"/**").access("permitAll");
}
@Override
protectedvoidconfigure(HttpSecurityhttp)throwsException{
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
protectedvoidconfigure(HttpSecurityhttp)throwsException{
http.authorizeRequests()
.antMatchers("/design","/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”,"/**").access("permitAll")
.and()
.formLogin()
.loginPage("/login");
}
//增加视图处理器
@OverridepublicvoidaddViewControllers(ViewControllerRegistryregistry){
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攻击,你只需要在传递的时候加上:
当然,你也可以关闭,但是不建议这样做:
.and() .csrf() .disable()
知道用户是谁
仅仅控制用户登录有时候是不够的,你可能还想在程序的其它地方获取已经登录的用户信息,有几种方式可以做到:
- 将Principal对象注入控制器方法
- 将Authentication对象注入控制器方法
- 使用SecurityContextHolder获取安全上下文
- 使用@AuthenticationPrincipal注解方法
1.将Principal对象注入控制器方法
@PostMappingpublicStringprocessOrder(@ValidOrderorder,Errorserrors,SessionStatussessionStatus,Principalprincipal){
...
Useruser=userRepository.findByUsername(principal.getName());
order.setUser(user);
...
}
2.将Authentication对象注入控制器方法
@PostMappingpublicStringprocessOrder(@ValidOrderorder,Errorserrors,SessionStatussessionStatus,Authenticationauthentication){
...
Useruser=(User)authentication.getPrincipal();
order.setUser(user);
...
}
3.使用SecurityContextHolder获取安全上下文
Authenticationauthentication= SecurityContextHolder.getContext().getAuthentication(); Useruser=(User)authentication.getPrincipal();
4.使用@AuthenticationPrincipal注解方法
@PostMappingpublicStringprocessOrder(@ValidOrderorder,Errorserrors,SessionStatussessionStatus,@AuthenticationPrincipalUseruser){
if(errors.hasErrors()){
return"orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return"redirect:/";
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。