面试题答案
一键面试1. 配置用户存储
- 基于内存存储:
- 在Spring Boot项目中,引入Spring Security依赖后,可以在
SecurityConfig
类中配置基于内存的用户存储。 - 示例代码:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Bean @Override public UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); UserDetails admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } }
- 在上述代码中,通过
InMemoryUserDetailsManager
创建了两个用户,一个是普通用户user
,一个是管理员用户admin
,并为他们分配了相应的角色。
- 在Spring Boot项目中,引入Spring Security依赖后,可以在
- 基于数据库存储:
- 首先,创建一个实现
UserDetailsService
接口的类,用于从数据库中加载用户信息。假设使用Spring Data JPA,定义一个User
实体类和UserRepository
接口。 User
实体类示例:
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import java.util.Set; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private Set<Role> roles; // getters and setters }
Role
实体类示例:
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // getters and setters }
UserRepository
接口示例:
import org.springframework.data.jpa.repository.JpaRepository; import com.example.demo.model.User; public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); }
- 实现
UserDetailsService
接口:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.example.demo.model.User; import com.example.demo.repository.UserRepository; @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } // 将数据库中的用户信息转换为Spring Security的UserDetails org.springframework.security.core.userdetails.User springUser = org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder() .username(user.getUsername()) .password(user.getPassword()) .roles(user.getRoles().stream().map(Role::getName).toArray(String[]::new)) .build(); return springUser; } }
- 在
SecurityConfig
类中使用自定义的UserDetailsService
:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Bean @Override public UserDetailsService userDetailsService() { return userDetailsService; } }
- 首先,创建一个实现
2. 认证流程
- 请求拦截:
- Spring Security通过过滤器链来拦截请求。当一个请求进入应用时,首先经过一系列的过滤器,其中
UsernamePasswordAuthenticationFilter
用于处理基于表单的认证。 - 该过滤器会从请求中提取用户名和密码(通常是从
login
表单的username
和password
字段),并将其封装成一个UsernamePasswordAuthenticationToken
对象。
- Spring Security通过过滤器链来拦截请求。当一个请求进入应用时,首先经过一系列的过滤器,其中
- 认证处理:
- 这个
UsernamePasswordAuthenticationToken
对象会被传递给AuthenticationManager
进行认证。AuthenticationManager
是Spring Security的核心接口之一,负责实际的认证逻辑。 - 通常情况下,
AuthenticationManager
会委托给UserDetailsService
的实现类(如前面配置的基于内存或数据库的UserDetailsService
)来加载用户信息,并与提交的用户名和密码进行比对。 - 如果认证成功,
AuthenticationManager
会返回一个已认证的Authentication
对象,该对象包含了用户的详细信息,如用户名、角色等。 - 如果认证失败,会抛出相应的异常,如
BadCredentialsException
(用户名或密码错误)等,Spring Security会根据配置来处理这些异常,通常会返回一个HTTP 401 Unauthorized响应,并引导用户重新登录。
- 这个
3. 授权策略制定
- 基于URL的授权:
- 在
SecurityConfig
类的configure(HttpSecurity http)
方法中,可以通过authorizeRequests()
来定义基于URL的授权策略。 - 示例:
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); }
- 在上述代码中,
/
和/home
路径允许所有用户访问(permitAll()
),/admin/**
路径只允许具有ADMIN
角色的用户访问(hasRole("ADMIN")
),其他所有路径要求用户必须经过认证(authenticated()
)。
- 在
- 基于方法的授权:
- 首先,在Spring Boot项目中启用方法级别的安全配置,在
SecurityConfig
类中添加@EnableGlobalMethodSecurity(prePostEnabled = true)
注解。 - 然后,在需要进行授权控制的服务方法上使用注解,如
@PreAuthorize
。 - 示例:
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @Service public class SomeService { @PreAuthorize("hasRole('ADMIN')") public void adminOnlyMethod() { // 只有ADMIN角色的用户可以调用此方法 } }
- 首先,在Spring Boot项目中启用方法级别的安全配置,在
4. 处理不同角色的访问权限控制
- 角色继承:
- Spring Security本身不直接支持角色继承,但可以通过自定义逻辑来实现。例如,可以创建一个自定义的
RoleHierarchy
实现。 - 首先,定义角色层次关系,如
admin > user
,表示admin
角色拥有user
角色的所有权限。 - 配置
RoleHierarchy
:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER"); return roleHierarchy; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } }
- 在上述代码中,
/user/**
路径允许ADMIN
和USER
角色访问,由于角色层次关系ROLE_ADMIN > ROLE_USER
,ADMIN
角色自然也能访问USER
角色可访问的路径。
- Spring Security本身不直接支持角色继承,但可以通过自定义逻辑来实现。例如,可以创建一个自定义的
- 动态权限控制:
- 可以通过实现
FilterInvocationSecurityMetadataSource
接口来实现动态权限控制。该接口的实现类负责根据当前请求的URL等信息,动态地获取该请求所需的权限。 - 首先,定义一个
SecurityMetadataSource
实现类,例如:
import org.springframework.security.access.ConfigAttribute; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.HashMap; import java.util.Map; @Component public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private static Map<String, Collection<ConfigAttribute>> resourceMap = null; public CustomSecurityMetadataSource() { loadResourceDefine(); } private void loadResourceDefine() { resourceMap = new HashMap<>(); // 示例配置,/admin/**路径需要ADMIN角色 Collection<ConfigAttribute> adminAttributes = SecurityConfig.createList("ROLE_ADMIN"); resourceMap.put("/admin/**", adminAttributes); // 其他路径配置... } @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) object).getRequestUrl(); for (String url : resourceMap.keySet()) { if (requestUrl.matches(url)) { return resourceMap.get(url); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } }
- 然后,创建一个
AccessDecisionManager
实现类,用于根据SecurityMetadataSource
返回的权限和当前用户的权限进行决策。
import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Iterator; @Component public class CustomAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (configAttributes == null) { return; } Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while (iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); String needRole = configAttribute.getAttribute(); for (GrantedAuthority ga : authentication.getAuthorities()) { if (needRole.equals(ga.getAuthority())) { return; } } } throw new AccessDeniedException("Access Denied!"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
- 最后,在
SecurityConfig
类中配置FilterInvocationSecurityMetadataSource
和AccessDecisionManager
:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private FilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired private AccessDecisionManager accessDecisionManager; @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER"); return roleHierarchy; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor(); filterSecurityInterceptor.setSecurityMetadataSource(securityMetadataSource); filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager); http.addFilterBefore(filterSecurityInterceptor, FilterSecurityInterceptor.class); } }
- 通过上述配置,可以根据不同的请求URL动态地决定所需的角色权限,实现更灵活的访问权限控制。
- 可以通过实现