认证是指,用户是否在本系统,以及账号信息是否符合设定的预期。
实现UserDetailsService
接口,根据用户名称,查询用户信息。可实现自定义查询用户,并设置用户的角色信息。
实现了该接口,需要到配置对应的密码加密类
已实现对用户密码的校验。因前端输入的密码是未加密,而数据库保存的密码是已加密的。
@Service
public class CustomerUserDetailsService implements UserDetailsService {
//密码加密类
@Autowired
private PasswordEncoder passwordEncoder;
//加载用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
CustomerUserDetails userDetails = new CustomerUserDetails();
userDetails.setUsername("user");
userDetails.setPassword(passwordEncoder.encode("123123"));
userDetails.setEnable(true);
userDetails.setAuthorities(Collections.emptySet());
userDetails.setAccountNonExpired(true);
userDetails.setAccountNonLocked(true);
userDetails.setCredentialsNonExpired(true);
return userDetails;
}
/**
* 根据用户名,返回用户角色
*/
private Collection<? extends GrantedAuthority> loadRoleByUsername(String username){
Collection<SimpleGrantedAuthority> collection = new HashSet<>();
//测试数据
if("admin".equals(username)) {
collection.add(new SimpleGrantedAuthority("ADMIN"));
}else {
collection.add(new SimpleGrantedAuthority(username));
}
return collection;
}
/**
* 角色信息可以参考SimpleGrantedAuthority类
* 根据用户名, 返回用户组角色
*/
private void loadGroupRoleByUsername(String username){
}
}
只需要实现UserDetails接口即可,自定义参数不需要和接口调用类保持一致(set/get)但最好保持一致,该接口是UserDetailsService接口中返回参数。
public class CustomerUserDetails implements UserDetails {
//用户密码
private String password;
//用户名
private String username;
//用户角色信息
private Collection<? extends GrantedAuthority> authorities;
//启用
private boolean enable;
//认证未过期
private boolean credentialsNonExpired;
//账号未锁定
private boolean accountNonExpired;
//账号未锁定
private boolean accountNonLocked;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return this.enable;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
}
因使用密码加密类,加密了密码,需要在配置中配置密码类。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
//设置密码加密类
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
资源认证,一般是基于URL与角色的认证。
需要自己定义
通过数据库定义,URL和角色的授权关系。在将数据加入的缓存中已减少多次读取的问题。
设计的时候:
/**
* 访问决策需要访问的资源
*/
@Component
public class CustomerFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
private Map<String,Collection<ConfigAttribute>> urlAndRoles = new HashMap<>();
/** 不校验即可请求 */
private String[] permitAllUrl;
/** 所有角色 */
private List<SecurityConfig> roles = new ArrayList<>();
/** 无角色Spring生成的角色名称*/
private SecurityConfig defaultRole = new SecurityConfig("ROLE_ANONYMOUS");
public void defaultAttributes(String... attributes){
this.permitAllUrl = attributes;
refresh();
}
public void refresh(){
for(String attribute : permitAllUrl){
Collection<ConfigAttribute> collection = new HashSet<>();
collection.add(defaultRole);
collection.addAll(roles);
urlAndRoles.put(attribute,collection);
}
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String url = ((FilterInvocation) object).getRequestUrl();
int index = url.indexOf("?");
if(index != -1){
url = url.substring(0,index);
}
Collection<ConfigAttribute> collection = getRoleByUrl(url);
//如果为空,则不进行校验
if(collection.isEmpty()){
}
return collection;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<>();
return allAttributes;
}
/**
* 获取请求路径的角色
*
*/
public Collection<ConfigAttribute> getRoleByUrl(String url) {
//收集 匹配到路径的角色
Collection<ConfigAttribute> collection = new HashSet<>();
//获取路径和角色的资源,一般放在缓存中
Iterator<String> iterator = urlAndRoles.keySet().iterator();
//这个角色是方便测试加入的,
collection.add(new SecurityConfig("ADMIN"));
while (iterator.hasNext()){
String matchUrl = iterator.next();
if(this.antPathMatcher.match(matchUrl,url)){
Collection<ConfigAttribute> matchCollection = urlAndRoles.get(matchUrl);
collection.addAll(matchCollection);
}
}
return collection;
}
/**
* 是否支持
*/
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
在获取URL对应的角色时,会将用户的角色和获取的角色进行比较,查看用户是否有权限访问。
/**
* 访问决策管理
* 讲通过URL获取到对应授权的角色,和当前用户拥有角色进行比较。 查看是否拥有请求当前资源权限
*/
@Component
public class CustomerAccessDecisionManager implements AccessDecisionManager {
protected final Log logger = LogFactory.getLog(getClass());
//一般进行角色校验
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
logger.info("decide");
while (iterator.hasNext()){
ConfigAttribute attribute = iterator.next();
for(GrantedAuthority ga : authentication.getAuthorities()){
if(attribute.getAttribute().equals(ga.getAuthority())){
return;
}
}
}
//返回访问拒绝信息
throw new AccessDeniedException("Access reject!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
需要继承FilterSecurityInterceptor类,在这里设置安全元数据、访问决策管理。如果不需要的访问,可以通过重写进行覆盖父类的方法。
@Component
public class CustomerFilterSecurityInterceptor extends FilterSecurityInterceptor {
//初始化方法,加载访问决策管理,和安全元数据
/**
@PostConstruct
public void init(){
logger.info("init info");
super.setSecurityMetadataSource(new CustomerFilterInvocationSecurityMetadataSource());
super.setAccessDecisionManager(new CustomerAccessDecisionManager());
}
*/
@Autowired
@Override
//自动注入自定义安全元数据
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource source){
super.setSecurityMetadataSource(source);
}
@Autowired
@Override
//自动注入自定义访问决策
public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager){
super.setAccessDecisionManager(accessDecisionManager);
}
/**
* 这一步可以不重写,使用系统自带的方法,如果不需要或是没有配置那些功能,则可以重写为自己想要的功能
*/
@Override
public void invoke(FilterInvocation fi) throws IOException, ServletException {
logger.info("invoke");
InterceptorStatusToken token = beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
在用户认证,安全配置类中加入,安全拦截器功能
/**
* 配置类
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected final Log logger = LogFactory.getLog(getClass());
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
logger.info("configure(WebSecurity web)");
//web.securityInterceptor(new CustomerFilterSecurityInterceptor());
web.securityInterceptor(this.getApplicationContext().getBean(FilterSecurityInterceptor.class));
}
}
第二种配置方法
无需创建FilterSecurityInterceptor
类
@Override
public void init(WebSecurity web) throws Exception {
HttpSecurity http = this.getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = (FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
AccessDecisionManager accessDecisionManager = this.getApplicationContext().getBean(AccessDecisionManager.class);
CustomerFilterInvocationSecurityMetadataSource securityMetadataSource = this.getApplicationContext().getBean(CustomerFilterInvocationSecurityMetadataSource.class);
securityMetadataSource.defaultAttributes("/login/**","/oauth/**","/test/**"); //不需要认证
securityInterceptor.setSecurityMetadataSource(securityMetadataSource);
securityInterceptor.setAccessDecisionManager(accessDecisionManager);
System.out.println("执行时间init");
web.securityInterceptor(securityInterceptor);
});
}
/**
* 配置HTTP安全信息
* 主要是请求路径的允许
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll()
.and().authorizeRequests()
.anyRequest().authenticated() //任何请求都需要认证
.and().csrf().disable() //关闭csrf
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}