springboot2.1.3整合spring-security,实现自定义授权决策
项目地址
项目地址:https://gitee.com/xuelingkang/spring-boot-demo
完整配置参考com.example.security包
maven依赖
<!-- security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
实现UserDetails
package com.example.model.vo;
import com.example.model.po.Resource;
import com.example.model.po.Role;
import com.example.model.po.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.jsonwebtoken.lang.Collections;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Data
@NoArgsConstructor
public class UserDetailsImpl implements UserDetails, Serializable {
private static final long serialVersionUID = 1L;
private User user;
private String token;
/** 登陆时间戳 */
private Long loginTime;
/** 过期时间戳 */
private Long expireTime;
public UserDetailsImpl(User user) {
this.user = user;
}
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<SimpleGrantedAuthority> authorities = new HashSet<>();
List<Role> roles = user.getRoles();
if (!Collections.isEmpty(roles)) {
for (Role role: roles) {
List<Resource> resources = role.getResources();
if (!Collections.isEmpty(resources)) {
for (Resource resource: resources) {
authorities.add(new SimpleGrantedAuthority(String.valueOf(resource.getId())));
}
}
}
}
return authorities;
}
@Override
@JsonIgnore
public String getPassword() {
return user.getPassword();
}
@Override
@JsonIgnore
public String getUsername() {
return user.getUsername();
}
// 账户是否未过期
@Override
@JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
// 账户是否未锁定
@Override
@JsonIgnore
public boolean isAccountNonLocked() {
return true;
}
// 密码是否未过期
@Override
@JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
// 账户是否激活
@Override
@JsonIgnore
public boolean isEnabled() {
return true;
}
}
实现UserDetailsService
package com.example.service.impl;
import com.example.model.po.User;
import com.example.model.vo.UserDetailsImpl;
import com.example.params.Params;
import com.example.service.IUserService;
import org.apache.commons.lang.StringUtils;
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;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private IUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)) {
throw new UsernameNotFoundException("用户名username不能为空!");
}
User user = userService.customGetOne(new Params<>(new User().setUsername(username)));
if (user==null) {
throw new UsernameNotFoundException("用户名:"+ username + "不存在!");
}
return new UserDetailsImpl(user);
}
}
主配置类
package com.example.config;
import com.example.filter.TokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler; //认证成功
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler; // 认证失败
@Autowired
private LogoutSuccessHandler logoutSuccessHandler; // 登出成功
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint; // 认证异常
@Autowired
private AccessDeniedHandler accessDeniedHandler; // 授权失败
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService; // 自己实现的UserDetailsService
@Autowired
private TokenFilter tokenFilter; // token过滤器,处理客户端的token参数
@Autowired
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource; // 授权拦截器,根据url获取所需权限
@Autowired
private AccessDecisionManager accessDecisionManager; // 授权决策
// 密码加密方式
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 定义认证用户信息获取来源,密码校验规则等
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
// 定义安全策略
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭csrf
http.csrf().disable();
// 基于token,所以不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
// 放开所有路径
.antMatchers("/**").permitAll()
// 真正的授权决策
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(accessDecisionManager);
return o;
}
});
http.formLogin()
.loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler);
http.exceptionHandling()
// 授权异常
.accessDeniedHandler(accessDeniedHandler)
// 认证异常
.authenticationEntryPoint(authenticationEntryPoint);
http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 解决不允许显示在iframe的问题
http.headers().frameOptions().disable();
http.headers().cacheControl();
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
授权决策涉及的主要类
package com.example.security;
import com.example.model.po.Resource;
import com.example.service.IResourceService;
import io.jsonwebtoken.lang.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.List;
// 根据访问url获取所需权限
@Component
public class FilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource {
@Value("${resource.type.http}")
private String http;
@Autowired
private IResourceService resourceService;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
// 查询所有http类型的资源
List<Resource> resources = resourceService.getByType(http);
Resource resource = null;
// 按照url匹配
if (!Collections.isEmpty(resources)) {
for (Resource r: resources) {
RequestMatcher requestMatcher = new AntPathRequestMatcher(r.getResourcePattern(), r.getResourceMethod());
if (requestMatcher.matches(request)) {
resource = r;
}
}
}
if (resource==null) {
return null;
}
return SecurityConfig.createList(String.valueOf(resource.getId()));
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return false;
}
}
package com.example.security;
import com.example.service.ITokenService;
import io.jsonwebtoken.lang.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
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.List;
// 自定义授权决策
@Component
public class AccessDecisionManagerImpl implements AccessDecisionManager {
@Autowired
private ITokenService tokenService;
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
// 当前用户资源
Collection<? extends GrantedAuthority> authorities;
if (authentication instanceof AnonymousAuthenticationToken) {
// 游客资源
authorities = tokenService.getGuest().getAuthorities();
} else {
// 当前用户资源
authorities = authentication.getAuthorities();
}
if (!Collections.isEmpty(configAttributes) && !Collections.isEmpty(authorities)) {
// 需求资源id
String resourceId = ((List<ConfigAttribute>) configAttributes).get(0).getAttribute();
// 遍历当前角色资源
for (GrantedAuthority authority: authorities) {
if (authority.getAuthority().equals(resourceId)) {
// 如果匹配到表示用户有权限
return;
}
}
}
// 没有匹配到表示权限不足
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}