問題の説明:
SpringSecurityを使用してユーザーの権限を検証する場合、カスタム権限制御マネージャーは、決定(認証認証、オブジェクトオブジェクト、Collection <ConfigAttribute> configAttributes)メソッドとgetAttributes(オブジェクトオブジェクト)メソッドを常に実行できるわけではありません。
最初は、2つの実装クラスがSpringコンテナーに追加されていないと思いましたが、後でそれらのコンストラクターが追加されました。プロジェクトが開始されたときに、コンテナーに既にあることを示す構築が実行されました。
問題の原因
この問題は1日以上悩みました。shiroやspringsecurityなどの承認フレームワークを使用したことを本当に後悔しています。自分で書いてビジネスに戻るのは非常に簡単です。その理由は、WebSecurityConfigの構成エラーです。正しい構成コードを投稿させてください(重要な部分は多すぎないので、自分で読まずに、分析を見てください)
package com.luntek.security.springsecurity.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.luntek.security.springsecurity.entity.User;
import com.luntek.security.springsecurity.service.impl.PasswordEncoderImpl;
import com.luntek.security.springsecurity.util.JsonUtil;
import com.luntek.security.springsecurity.util.JwtTokenUtil;
import com.luntek.security.springsecurity.util.RedisOperator;
import com.luntek.security.springsecurity.util.ResponseResult;
import lombok.extern.slf4j.Slf4j;
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.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义的用户名密码认证配置类
*
* @Date 2020/3/5 0005 下午 2:32
* @Created by Czw
*/
@Slf4j
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Qualifier("userDetailsService")
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private RedisOperator redisOperator;
@Autowired
@Qualifier("customAccessDecisionManager")
private AccessDecisionManager accessDecisionManager;
@Autowired
@Qualifier("customFilterInvocationSecurityMetadataSource")
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
//密码编码器,对比账号密码前的密码加密规则,使用自定义的密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoderImpl();
}
//权限提供者
@Bean
public AuthenticationProvider authenticationProvider() {
log.info("***authenticationProvider***");
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
//对默认的UserDetailsService进行覆盖
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
/**
* 提供User的 bean
*
* @auther Czw
* @date 2020/3/16 0016 上午 11:31
*/
@Bean
public User getUser() {
return new User();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//基于数据库查数据
log.info("***WebSecurityConfig.configure()基于数据库查数据***");
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
//从数据库中动态获取后添加
@Override
protected void configure(HttpSecurity http) throws Exception {
log.info("***WebSecurityConfig.configure()拦截机制***");
AuthenticationProvider authenticationProvider = authenticationProvider();
http.authenticationProvider(authenticationProvider)
.httpBasic()
//未登录时,进行json格式的提示,不用单独写一个又一个的类
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString(new ResponseResult<>(302, "未登录或已过期,请重新登录")));
out.flush();
out.close();
})
.and()
.authorizeRequests()
.anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
.and()
.formLogin() //使用自带的登录
.permitAll()
//登录失败,返回json
.failureHandler((request, response, ex) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = response.getWriter();
ResponseResult responseResult = new ResponseResult<String>();
responseResult.setState(4004);
responseResult.setSuccess(false);
if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
responseResult.setMessage("用户名或密码错误");
} else if (ex instanceof DisabledException) {
responseResult.setMessage("账号被禁用");
} else {
responseResult.setMessage("登录失败");
}
out.write(objectMapper.writeValueAsString(responseResult));
out.flush();
out.close();
})
//登录成功,返回json
.successHandler((request, response, authentication) -> {
sendSuccessResponse(response, authentication);
})
.and()
.exceptionHandling()
//没有权限,返回json
.accessDeniedHandler((request, response, ex) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString(new ResponseResult<>(403, "用户权限不足")));
out.flush();
out.close();
})
.and()
.logout()
//退出成功,返回json
.logoutSuccessHandler((request, response, authentication) -> {
sendLogoutResponse(response, authentication);
})
.permitAll();
//开启跨域访问
http.cors().disable();
//开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
http.csrf().disable();
}
/**
* 忽略拦截的路径
*
* @param web
*/
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/ignore");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
private void sendSuccessResponse(HttpServletResponse response, Authentication authentication) throws IOException {
response.setContentType("application/json;charset=utf-8");
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
PrintWriter out = response.getWriter();
String mobile = userDetails.getUsername();
String token = JwtTokenUtil.createToken(mobile);
//将 用户信息 & 权限信息 放入到Redis中
redisOperator.set(RedisOperator.USER_INFO_SUFFIX + mobile, JsonUtil.objectToJson(userDetails));
List<String> permissionList = new ArrayList<>();
for (GrantedAuthority authority : userDetails.getAuthorities()) {
permissionList.add(authority.getAuthority());
}
redisOperator.set(RedisOperator.USER_PERMISSION + mobile, JsonUtil.objectToJson(permissionList));
out.write(objectMapper.writeValueAsString(new ResponseResult<>(token)));
out.flush();
out.close();
}
private void sendLogoutResponse(HttpServletResponse response, Authentication authentication) throws IOException {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String mobile = userDetails.getUsername();
redisOperator.delByKey(RedisOperator.USER_INFO_SUFFIX + mobile);
redisOperator.delByKey(RedisOperator.USER_PERMISSION + mobile);
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString(new ResponseResult<>(200, "系统登出成功")));
out.flush();
out.close();
}
/**
* 自定义 FilterSecurityInterceptor ObjectPostProcessor 以替换默认配置达到动态权限的目的
*
* @return ObjectPostProcessor
*/
private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
return new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(accessDecisionManager);
object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
return object;
}
};
}
}
ログインの成功と失敗はすべてそこに記述されているため、コードは少し多すぎます。分析は、実際には、多くの重要なコードはなく、10行未満です。アクセスできない理由は、設定
.authorizeRequests().anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
がないためです。withObjectPostProcessorパラメータが設定されていないため、コードを実行できませんでした。
問題が解決しました
withObjectPostProcessorパラメータを追加し、filterSecurityInterceptorObjectPostProcessor()メソッドを追加して、このメソッドのコンテンツをポストします。実際には、2つの実装クラスをObjectPostProcessorに設定します
@Autowired
private AccessDecisionManager accessDecisionManager;
@Autowired
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
/**
* 自定义 FilterSecurityInterceptor ObjectPostProcessor
* 以替换默认配置达到动态权限的目的
*
* @return ObjectPostProcessor
*/
private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
return new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(accessDecisionManager);
object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
return object;
}
};
}
エピローグ:この時点で問題は解決され、2つのクラスのメソッドを実行できます。その中でも、上記のObjectPostProcessorは、実装クラスのメソッドを使用して実装することもできます。以下のように、ObjectPostProcessor <FilterSecurityInterceptor>インターフェースを実装してpostProcessメソッドを書き直します。個人的には問題ないと思いますが、起動時に常にNoUniqueBeanDefinitionException(唯一のBeanではない)を報告します。ここに理由を見つける時間はありません。上記の内部クラスが使用されます。道
package com.luntek.security.springsecurity.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.stereotype.Component;
/**
* @Date 2020/3/16 0016 下午 3:45
* @Created by Czw
*/
@Qualifier("mySecurityObjectPostProcessor")
@Component
public class SecurityObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
fsi.setAccessDecisionManager(new CustomAccessDecisionManager()); //权限决策处理类
fsi.setSecurityMetadataSource(new CustomFilterSecurityMetadataSource()); //路径(资源)拦截处理
return fsi;
}
}
lzは、データベース、ユーザー、ロール、権限、およびspringsecurity + jwt + springbootの2つのリレーショナルテーブルのデモを書いています。必要に応じて、非公開でチャットしたり、後で投稿するcsdnをフォローしたりできます。この問題を見つけるのに1日以上かかりました。気に入ってください。