SpringSecurity实现自定义登录认证、权限验证、鉴权

Demo源码:https://github.com/ygsama/ipa

自定义登录认证的实现

需要实现三个接口
UserDetails 用户类接口
UserDetailsService 查询用户密码的service 接口
AuthenticationProvider 为认证管理器AuthenticationManager 提供验证组件AuthenticationProvider

/**
 * 实现了 {@link UserDetails}接口
 * 用于构建存储在SecurityContextHolder的Authentication对象
 *
 * @author ygsama
 */
@Slf4j
@Data
public class SysUserDO implements UserDetails {

	private String username;

	private String password;

	private String name;

	private List<SysRoleDO> roleList;

	// ... 其他字段省略

	/**
	 * 装填用户的角色列表
	 */
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		if (roleList == null || roleList.size() < 1) {
			return AuthorityUtils.commaSeparatedStringToAuthorityList("");
		}
		log.info("[原始用户角色列表装填]: ", roleList);
		StringBuilder roles = new StringBuilder();
		for (SysRoleDO role : roleList) {
			roles.append("ROLE_").append(role.getNo()).append(",");
		}
		List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(roles.substring(0, roles.length() - 1));
		log.info("[遍历并返回用户的角色列表]: {}", authorityList);
		return authorityList;
	}

	@Override
	public String getPassword() {
		return this.password;
	}

	@Override
	public String getUsername() {
		return this.username;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}
/**
 * 用户登录的service实现类 
* 框架的默认实现是{@link JdbcDaoImpl}
* * @author ygsama */
@Slf4j @Service("userDetailsService") public class LoginUserDetailsServiceImpl implements UserDetailsService { private final AuthUserMapper authUserMapper; private final AuthRoleMapper authRoleMapper; @Autowired public LoginUserDetailsServiceImpl(AuthUserMapper authUserMapper, AuthRoleMapper authRoleMapper) { this.authUserMapper = authUserMapper; this.authRoleMapper = authRoleMapper; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUserDO sysUserDO = authUserMapper.selectByPrimaryKey(username); if (sysUserDO == null) { throw new UsernameNotFoundException(username); } List<SysRoleDO> sysRoles = authRoleMapper.qryUserRoleByUsername(username); sysUserDO.setRoleList(sysRoles); log.info("[loadUserByUsername]: {}", sysUserDO); return sysUserDO; } }
/**
 * 登录认证的Provider,自定义实现了{@link AuthenticationProvider} 
* Provider默认实现是 {@link DaoAuthenticationProvider}
* {@link BearerTokenAuthenticationFilter} 装填=> {@link Authentication}对象
* {@link UsernamePasswordAuthenticationFilter} 调用=> {@link AuthenticationManager} => {@link AuthenticationProvider}验证
* * @author ygsama */
@Slf4j @Component public class LoginAuthenticationProvider implements AuthenticationProvider { private final LoginUserDetailsServiceImpl userDetailService; private final PasswordEncoder passwordEncoder; @Autowired public LoginAuthenticationProvider(LoginUserDetailsServiceImpl userDetailService, PasswordEncoder passwordEncoder) { this.userDetailService = userDetailService; this.passwordEncoder = passwordEncoder; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // http请求的账户密码 String username = authentication.getName(); String password = (String) authentication.getCredentials(); // 数据库根据用户名查询 UserDetails userDetails = userDetailService.loadUserByUsername(username); log.info("[http请求的账户密码]: {}/{}", username, password); log.info("[数据库查询的账户密码]:{} ", userDetails); if (userDetails == null) { throw new BadCredentialsException("用户名未找到"); } if (!passwordEncoder.matches(password, userDetails.getPassword())) { throw new BadCredentialsException("密码不正确"); } Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); log.info("[设置authorities] : {}", authorities); return new UsernamePasswordAuthenticationToken(userDetails, password, authorities); } /** * 是否支持处理当前Authentication对象类似 */ @Override public boolean supports(Class<?> authentication) { return true; } }

自定义权限验证的实现

SpringSecurity自定义权限验证时也需要实现三个接口:
AccessDecisionManager : 自定义鉴权管理器,根据URL资源权限用户角色权限进行鉴权
FilterInvocationSecurityMetadataSource:自定义权限数据源,提供所有URL资源对应角色权限映射集合
AbstractSecurityInterceptor :资源访问过滤器,拦截访问请求,封装成安全对象FilterInvocation,调用前两个实例进行鉴权

/**
 * {@link AccessDecisionManager} 鉴权决策管理器 
* 被鉴权决策管理器 被{@link AbstractSecurityInterceptor} 调用进行鉴权
* 框架默认实现是 {@link UnanimousBased} * * @author ygsama */
@Slf4j @Service public class AccessDecisionManagerImpl implements AccessDecisionManager { /** * 权限鉴定 * * @param authentication from SecurityContextHolder.getContext() =》 userDetails.getAuthorities() * @param object 是一个安全对象类型,FilterInvocation.class * @param configAttributes from MetaDataSource.getAttributes(),已经被框架做了非空判断 */ @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { log.info("[资源权限]: {}", configAttributes); log.info("[用户权限]: {}", authentication.getAuthorities()); Iterator<ConfigAttribute> it = configAttributes.iterator(); while (it.hasNext()) { // 资源的权限 ConfigAttribute resourceAttr = it.next(); String resourceRole = "ROLE_" + ((SecurityConfig) resourceAttr).getAttribute(); // 用户的权限 for (GrantedAuthority userAuth : authentication.getAuthorities()) { log.info("[资源角色==用户角色] ? {} == {}", resourceRole.trim(), userAuth.getAuthority().trim()); if (resourceRole.trim().equals(userAuth.getAuthority().trim())) { return; } } } throw new AccessDeniedException("权限不足"); } /** * 被AbstractSecurityInterceptor调用,遍历ConfigAttribute集合,筛选出不支持的attribute */ @Override public boolean supports(ConfigAttribute attribute) { return true; } /** * 被AbstractSecurityInterceptor调用,验证AccessDecisionManager是否支持这个安全对象的类型。 */ @Override public boolean supports(Class<?> clazz) { return true; } }
/**
 * 权限资源映射的数据源 
* 这里重写并实现了基于数据库的权限数据源
* 实现了 {@link FilterInvocationSecurityMetadataSource}接口
* 框架的默认实现是 {@link DefaultFilterInvocationSecurityMetadataSource}
* * @author ygsama */
@Slf4j @Service public class AccessSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { // key 是url+method , value 是对应url资源的角色列表 private static Map<RequestMatcher, Collection<ConfigAttribute>> permissionMap; // 从数据库取出权限数据,代码略 private AuthRoleMapper authRoleMapper; private AuthPermissionMapper authPermissionMapper; @Autowired AccessSecurityMetadataSource(AuthRoleMapper authRoleMapper, AuthPermissionMapper authPermissionMapper) { this.authPermissionMapper = authPermissionMapper; this.authRoleMapper = authRoleMapper; } /** * 在Web服务器启动时,缓存系统中的所有权限映射。
* 被{@link PostConstruct}修饰的方法会在服务器加载Servlet的时候运行(构造器之后,init()之前)
*/
@PostConstruct private void loadResourceDefine() { permissionMap = new LinkedHashMap<>(); // 需要鉴权的url资源,@needAuth标志 List<SysPermissionDO> permissionList = authPermissionMapper.queryRolePermission(); for (SysPermissionDO permission : permissionList) { String url = permission.getUrl(); String method = permission.getMethod(); String[] roles = permission.getRoleList().split(","); log.info("{} - {}", url, method); AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher(url, method); Collection<ConfigAttribute> attributes = new ArrayList<>(); for (String role : roles) { attributes.add(new SecurityConfig(role)); } // 占位符,需要权限才能访问的资源 都需要添加一个占位符,保证value不是空的 attributes.add(new SecurityConfig("@needAuth")); permissionMap.put(requestMatcher, attributes); } // 公共的url资源 & 系统接口的url资源,value为null List<SysPermissionDO> publicList = authPermissionMapper.queryPublicPermission(); for (SysPermissionDO permission : publicList) { String url = permission.getUrl(); String method = permission.getMethod(); AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher(url, "*".equals(method) ? null : method); // value为空时不做鉴权,相当于所有人都可以访问该资源URL permissionMap.put(requestMatcher, null); } // 多余的url资源, @noAuth,所有人都无法访问 Collection<ConfigAttribute> attributes = new ArrayList<>(); attributes.add(new SecurityConfig("@noAuth")); permissionMap.put(new AntPathRequestMatcher("/**", null), attributes); log.info("[全局权限映射集合初始化]: {}", permissionMap.toString()); } /** * 鉴权时会被AbstractSecurityInterceptor.beforeInvocation()调用,根据URL找到对应需要的权限 * * @param object 安全对象类型 FilterInvocation.class */ @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { log.info("[资源被访问:根据URL找到权限配置]: {}\n {}", object, permissionMap); if (permissionMap == null) { loadResourceDefine(); } final HttpServletRequest request = ((FilterInvocation) object).getRequest(); for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : permissionMap.entrySet()) { if (entry.getKey().matches(request)) { log.info("[找到的Key]: {}", entry.getKey()); log.info("[找到的Value]: {}", entry.getValue()); if (entry.getValue().size() > 0) { return entry.getValue(); } } } return null; } /** * 用于被AbstractSecurityInterceptor调用,返回所有的 Collection ,以筛选出不符合要求的attribute */ @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return new ArrayList<>(); } /** * 用于被AbstractSecurityInterceptor调用,验证指定的安全对象类型是否被MetadataSource支持 */ @Override public boolean supports(Class<?> clazz) { return true; } }
/**
 * 资源访问过滤器 
* 重写了{@link AbstractSecurityInterceptor} 接口
* 默认的过滤器实现是{@link FilterSecurityInterceptor} * * @author ygsama */
@Slf4j @Component public class AccessSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { // 注入前面的两个实例 private final AccessSecurityMetadataSource accessSecurityMetadataSource; private final AccessDecisionManagerImpl accessDecisionManagerImpl; @Autowired public AccessSecurityInterceptor(AccessSecurityMetadataSource accessSecurityMetadataSource, AccessDecisionManagerImpl accessDecisionManagerImpl) { this.accessSecurityMetadataSource = accessSecurityMetadataSource; this.accessDecisionManagerImpl = accessDecisionManagerImpl; } /** * 初始化时将自定义的DecisionManager,注入到父类AbstractSecurityInterceptor中 */ @PostConstruct public void initSetManager() { super.setAccessDecisionManager(accessDecisionManagerImpl); } /** * 重写父类AbstractSecurityInterceptor,获取到自定义MetadataSource的方法 */ @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.accessSecurityMetadataSource; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("[自定义过滤器]: {}", " LoginSecurityInterceptor.doFilter()"); FilterInvocation fi = new FilterInvocation(request, response, chain); // 调用父类的 beforeInvocation ==> accessDecisionManager.decide(..) InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { // 调用父类的 afterInvocation ==> afterInvocationManager.decide(..) super.afterInvocation(token, null); } } /** * 向父类提供要处理的安全对象类型,因为父类被调用的方法参数类型大多是Object,框架需要保证传递进去的安全对象类型相同 */ @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public void init(FilterConfig filterConfig) { } @Override public void destroy() { } }

配置类

WebSecurityConfigurerAdapter 继承后将上面自定义的一些配置添加到框架中

如果是OAuth2 认证还需要继承实现以下两个配置类:
ResourceServerConfigurerAdapter
AuthorizationServerConfigurerAdapter

此处省略,详见源码:https://github.com/ygsama/ipa

你可能感兴趣的:(Java,Spring,Security)