3.登出
讲完登陆后再看登出就很好明白:
@Override protected void configure(HttpSecurity http) throws Exception { http // 其他配置 .logout() .logoutUrl("/logout") // 配置登出的URL .logoutSuccessUrl("/login") // 配置登出成功后的跳转URL .deleteCookies("remember-me") // 删除Cookie .permitAll(); // 允许所有用户访问登出URL }
4.使用数据库
上文中我们都是用auth.inMemoryAuthentication()将定义的用户和角色信息放在内存中,在实际业务场景中有时候我们是需要将这些信息放在数据库中进行持久化的。spring security中有两种常用方式来将信息放在数据库中:
- auth.jdbcAuthentication()
- auth.userDetailsService()
4.1.jdbcAuthentication
使用这种方式时,严格规定了表结构,具体的建表语句在这里:
这里要注意几点:
- 权限前缀要加ROLE_,不然不会生效,enabled属性是用户是否生效1是0否
- 密码一定要使用加密,不加密识别不了
接下来就是使用:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @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 authorities where username=?") .passwordEncoder(new BCryptPasswordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { // ... } }
4.2.userDetailsService
jdbcAuthentication这种方式中对表结构有严格要求,如果觉得太死,需要灵活一点,security还提供了userDetailsService这种方式灵活的来让我们灵活的使用。
我们可以自己实现UserDetailsService,重新loadUserByUsername方法,在方法中灵活的去将系统用户转为security的用户。
@Service public class UserServiceImpl implements UserDetailsService { @Autowired UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //从存储中获取系统用户 SystemUser systemUser = userDao.getUser(); //将系统用户的用户名密码来创建security的用户 User.UserBuilder builder = User.builder(); UserDetails user = builder.username(systemUser.getUserName()).password(systemUser.getPassword()).roles("admin").build(); return user; } }
5.自定义处理器
在Spring Security中,默认认证成功了就能访问到接口,但是如果我想认证成功后不直接访问接口而是做其他事情喃?spring security也提供了我们自定义操作完成后的处理过程的能力,我们可以使用自定义的处理器(Handler)来处理认证、授权、注销等操作。自定义处理器可以通过实现相应的接口或继承相应的类来完成。
以下是几个常用的自定义处理器的接口/类及其作用:
AuthenticationSuccessHandler:用于认证成功后的处理,比如记录日志、跳转到指定页面等。
AuthenticationFailureHandler:用于认证失败后的处理,比如记录日志、显示错误信息等。
AccessDeniedHandler:用于处理访问被拒绝的情况,比如跳转到错误页面、记录日志等。
LogoutSuccessHandler:用于注销成功后的处理,比如跳转到登录页面、删除Cookie等。
InvalidSessionStrategy:用于处理无效的Session,比如跳转到登录页面、记录日志等。
在Spring Security中,我们可以通过配置来使用自定义的处理器。例如:
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 处理认证成功的逻辑,比如记录日志、跳转页面等 response.sendRedirect("/index"); } }
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { // 处理认证失败的逻辑,比如记录日志、跳转页面等 response.sendRedirect("/login?error=true"); } }
public class MyLogoutHandler implements LogoutHandler { @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { // 处理注销的逻辑,比如记录日志、清除缓存等 } }
public class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 处理注销成功的逻辑,比如记录日志、跳转页面等 response.sendRedirect("/logoutSuccess"); } }
定义完后直接使用即可:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //省略其他配置... @Autowired private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; @Autowired private CustomAuthenticationFailureHandler customAuthenticationFailureHandler; @Autowired private CustomAccessDeniedHandler customAccessDeniedHandler; @Autowired private CustomLogoutSuccessHandler customLogoutSuccessHandler; @Autowired private CustomInvalidSessionStrategy customInvalidSessionStrategy; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() .and() .formLogin() .successHandler(customAuthenticationSuccessHandler) .failureHandler(customAuthenticationFailureHandler) .and() .exceptionHandling() .accessDeniedHandler(customAccessDeniedHandler) .and() .logout() .logoutSuccessHandler(customLogoutSuccessHandler) .and() .sessionManagement() .invalidSessionStrategy(customInvalidSessionStrategy); } }
6.更多细粒度的控制
前文我们都是基于角色来进行访问控制的,稍加思考就会想到,光是靠角色来控制实际应用中可能不够,有时候还要基于HTTP METHOD、IP、权限等等,spring security当然给我们提供了这些更加细粒度的访问控制方法:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // 配置安全规则 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 基于角色控制访问 .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") // 基于权限控制访问 .antMatchers("/article/**").hasAuthority("ARTICLE_VIEW") // 基于IP地址控制访问 .antMatchers("/profile/**").hasIpAddress("192.168.1.0/24") // 基于方法表达式控制访问 .antMatchers(HttpMethod.DELETE, "/article/**").access("hasRole('ADMIN') or hasAuthority('ARTICLE_DELETE')") .antMatchers(HttpMethod.PUT, "/article/**").access("hasRole('ADMIN') or hasAuthority('ARTICLE_EDIT')") // 其他请求需要进行身份认证 .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic(); } // 配置用户信息 @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() // 配置用户角色和权限 .withUser("user").password("{noop}password").roles("USER").authorities("ARTICLE_VIEW") .and() .withUser("admin").password("{noop}password").roles("ADMIN").authorities("ARTICLE_VIEW", "ARTICLE_EDIT", "ARTICLE_DELETE"); } }
当然除了代码里可以配置,也提供了注解版本:
// 基于角色的访问控制注解 @Secured("ROLE_ADMIN") public void adminOnlyMethod() { // 只有 ADMIN 角色的用户可以调用这个方法 } // 基于角色的访问控制注解 @RolesAllowed("ROLE_USER") public void userOnlyMethod() { // 只有 USER 角色的用户可以调用这个方法 } // 基于方法调用之后的返回值进行访问控制 @PostAuthorize("returnObject.owner == authentication.name") public Resource getResourceById(String id) { // 获取资源 return resource; } // 在方法调用前进行集合过滤 @PreFilter("filterObject.owner == authentication.name") public List<Resource> getResources(List<String> ids) { // 获取指定 ID 的资源列表 return resources; } // 在方法调用后进行集合过滤 @PostFilter("filterObject.owner == authentication.name") public List<Resource> getAllResources() { // 获取所有资源列表 return resources; } // 基于SpEL表达式的访问控制注解,控制HTTP方法 @PreAuthorize("hasRole('ADMIN') and #httpMethod == 'GET'") @GetMapping("/adminOnly") public ResponseEntity<String> adminOnlyEndpoint(HttpServletRequest request) { return ResponseEntity.ok("Only accessible to admins"); }
7.原理简述
看上图,Spring Security的实现原理一目了然,其实它就是通过一个Servlet过滤器来挂载到应用程序的请求流程中。当一个请求到达应用程序时,它首先会被Spring Security的过滤器拦截,进行安全控制处理,然后将请求继续传递给应用程序的其他组件进行处理。
这里面的投票器不用深究,它的作用其实就是用于决定一个用户是否有访问特定资源的权限。投票器通过对安全上下文(SecurityContext)中的身份验证信息、访问控制列表(ACL)和其他因素进行评估,决定是否允许用户访问资源。