如何使用SpringSecurity保护程序安全

网友投稿 400 2023-07-23

如何使用SpringSecurity保护程序安全

如何使用SpringSecurity保护程序安全

首先,引入依赖:

org.springframework.boot

spring-boot-starter-security

引入此依赖之后,你的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攻击,你只需要在传递的时候加上:

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

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

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Android设备如何保证数据同步写入磁盘的实现
下一篇:Spring Security OAuth2认证授权示例详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~