Spring Security默认使用「用户名/密码」的方式进行登陆校验,并通过cookie的方式存留登陆信息。在一些定制化场景,比如希望单独使用token串进行部分页面的访问权限控制时,默认方案无法支持。在未能在网上搜索出相关实践的情况下,通过官方文档及个别Stack Overflow的零散案例,形成整体思路并实践测试通过,本文即关于该方案的一个分享。
官方文档:https://docs.spring.io/spring-security/site/docs/5.0.5.BUILD-SNAPSHOT/reference/htmlsingle/
基本的SpringSecurity使用方式网上很多,不是本文关注的重点,如有需要可以自行搜索或参见https://blog.csdn.net/u012702547/article/details/54319508
关于校验的整个流程简单的说,整个链路有三个关键点,
完整的调用链建议在IDE中通过单步调试亲自体会,本文不做相关整理。
我的需求,是使用自定义的token,验证权限,涉及到:
需要做的事情如下:
/** * @author: Blaketairan */ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import java.util.ArrayList; import java.util.Collection; /** * Description: spring-security的Authentication的自定义实现(用于校验token) */ public class TokenAuthentication implements Authentication{ private String token; public TokenAuthentication(String token){ this.token = token; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return new ArrayList<GrantedAuthority>(0); } @Override public Object getCredentials(){ return token; } @Override public Object getDetails() { return null; } @Override public Object getPrincipal() { return null; } @Override public boolean isAuthenticated() { return true; } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { } @Override public String getName() { return null; } }
/** * @author: Blaketairan */ import com.google.common.base.Strings; import com.blaketairan.spring.security.configuration.TokenAuthentication; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * Description: 用于处理收到的token并为spring-security上下文生成及注入Authenticaion实例 */ @Configuration public class AuthenticationTokenFilter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException{ } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException{ if (servletRequest instanceof HttpServletRequest){ String token = ((HttpServletRequest) servletRequest).getHeader("PRIVATE-TOKEN"); if (!Strings.isNullOrEmpty(token)){ Authentication authentication = new TokenAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); System.out.println("Set authentication with non-empty token"); } else { /** * 在未收到Token时,至少塞入空TokenAuthenticaion实例,避免进入SpringSecurity的用户名密码默认模式 */ Authentication authentication = new TokenAuthentication(""); SecurityContextHolder.getContext().setAuthentication(authentication); System.out.println("Set authentication with empty token"); } } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy(){ } }
/** * @author: Blaketairan */ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; import java.io.Serializable; /** * Description: spring-security 自定义的权限处理模块(鉴权) */ public class SecurityPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication,Object targetDomainObject, Object permission){ String targetDomainObjectString = null; String permissionString = null; String token = null; try { targetDomainObjectString = (String)targetDomainObject; permissionString = (String)permission; token = (String)authentication.getCredentials(); } catch (ClassCastException e){ e.printStackTrace(); return false; } return hasPermission(token, targetDomainObjectString, permissionString); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission){ /** * 使用@PreAuthorize("hasPermission('TARGET','PERMISSION')")方式,不使用该鉴权逻辑 */ return false; } private boolean hasPermission(String token,String targetDomain, String permission){ /** * 验证权限 **/ return true; } }
/** * @author: Blaketairan */ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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; /** * Description: spring-security配置,指定使用自定义的权限评估方法 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Bean @Override protected AuthenticationManager authenticationManager() throws Exception{ return super.authenticationManager(); } @Bean public PermissionEvaluator permissionEvaluator() { /** * 使用自定义的权限验证 **/ SecurityPermissionEvaluator securityPermissionEvaluator = new SecurityPermissionEvaluator(); return securityPermissionEvaluator; } @Override protected void configure(HttpSecurity httpSecurity) throws Exception{ /** * 关掉csrf方便本地ip调用调试 **/ httpSecurity .csrf() .disable() .httpBasic() .disable(); } }
/** * @author: Blaketairan */ import org.springframework.security.access.prepost.PreAuthorize; import java.util.List; /** * Description: */ public interface BaseRepository{ @PreAuthorize("hasPermission('DOMAIN', 'PERMISSION')") void deleteAll(); }
希望对看到本文的人有所帮助。
如对本文有疑问, 点击进行留言回复!!
[杭电多校2020]第一场 1004 Distinct Sub-palindromes
Swift -- 将本地生成的UIImage进行持久化保存(存到文件中fileManager.createFile)
网友评论