跨域配置
http.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class);
跨域filter類
@Component
public class CorsFilter implements Filter {
private final static Logger log = LoggerFactory.getLogger(CorsFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//servletRequest.setCharacterEncoding("UTF-8");
HttpServletResponse res = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
Map<String, String[]> parameterMap = request.getParameterMap();
log.info(request.getMethod() + ">>>>>>>>>>>>>>>" + JSONObject.toJSONString(parameterMap));
String origin = request.getHeader("Origin");
log.info("請(qǐng)求origin:" + origin);
res.setHeader("Access-Control-Allow-Origin", "*");
res.setContentType("text/html;charset=UTF-8");
res.setHeader("Access-Control-Allow-Methods", "*");
res.setHeader("Access-Control-Max-Age", "86400");
res.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("XDomainRequestAllowed", "1");
filterChain.doFilter(servletRequest, servletResponse);
}
}
自定義UserAuthenticationFilter 繼承 UsernamePasswordAuthenticationFilter 替換掉原來的 UsernamePasswordAuthenticationFilter
http.addFilterAt(UserAuthenticationFilterBean(), UsernamePasswordAuthenticationFilter.class);
UserAuthenticationFilter 類
public class UserAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private ThreadLocal<Map<String,String>> threadLocal = new ThreadLocal<>();
@Override
protected String obtainPassword(HttpServletRequest request) {
String password = this.getBodyParams(request).get(super.getPasswordParameter());
if(!StringUtils.isEmpty(password)){
return password;
}
return super.obtainPassword(request);
}
@Override
protected String obtainUsername(HttpServletRequest request) {
String username = this.getBodyParams(request).get(super.getUsernameParameter());
if(!StringUtils.isEmpty(username)){
return username;
}
return super.obtainUsername(request);
}
/**
* 獲取body參數(shù) body中的參數(shù)只能獲取一次
* @param request
* @return
*/
private Map<String,String> getBodyParams(HttpServletRequest request){
Map<String,String> bodyParams = threadLocal.get();
if(bodyParams==null) {
ObjectMapper objectMapper = new ObjectMapper();
try (InputStream is = request.getInputStream()) {
bodyParams = objectMapper.readValue(is, Map.class);
} catch (IOException e) {
}
if(bodyParams==null) bodyParams = new HashMap<>();
threadLocal.set(bodyParams);
}
return bodyParams;
}
}
創(chuàng)建UserAuthenticationFilter
private UserAuthenticationFilter UserAuthenticationFilterBean() throws Exception {
UserAuthenticationFilter userAuthenticationFilter = new UserAuthenticationFilter();
userAuthenticationFilter.setAuthenticationManager(super.authenticationManager());
userAuthenticationFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
userAuthenticationFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
userAuthenticationFilter.setUsernameParameter("username");
userAuthenticationFilter.setPasswordParameter("password");
userAuthenticationFilter.setRememberMeServices(rememberMeServices());
return userAuthenticationFilter;
}
到這里遇到一個(gè)坑
后面想要加入rememberme 功能 按照正常的配置方法是不行的
因?yàn)閁sernamePasswordAuthenticationFilter已經(jīng)被我們替換掉了
所以只能自己去配置userAuthenticationFilter的RememberMeServices
創(chuàng)建remembermeservice
@Bean
public RememberMeServices rememberMeServices() {
PersistentTokenBasedRememberMeServices rememberMeServices = new PersistentTokenBasedRememberMeServices(REMEMBER_ME, myUserDetailsService, myRedisTokenRepository);
rememberMeServices.setParameter(REMEMBER_ME);
rememberMeServices.setTokenValiditySeconds(3600);
return rememberMeServices;
}
redis存儲(chǔ)實(shí)現(xiàn)類
@Component
public class MyRedisTokenRepository implements PersistentTokenRepository {
private final static long TOKEN_VALID_DAYS = 30;
private final static Logger log = LoggerFactory.getLogger(MyRedisTokenRepository.class);
@Autowired
private RedisTemplate redisTemplate;
@Override
public void createNewToken(PersistentRememberMeToken token) {
if (log.isDebugEnabled()) {
log.debug("token create seriesId: [{}]", token.getSeries());
}
String key = generateKey(token.getSeries());
HashMap<String, String> map = new HashMap();
map.put("username", token.getUsername());
map.put("tokenValue", token.getTokenValue());
map.put("date", String.valueOf(token.getDate().getTime()));
redisTemplate.opsForHash().putAll(key, map);
redisTemplate.expire(key, TOKEN_VALID_DAYS, TimeUnit.DAYS);
}
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
String key = generateKey(series);
HashMap<String, String> map = new HashMap();
map.put("tokenValue", tokenValue);
map.put("date", String.valueOf(lastUsed.getTime()));
redisTemplate.opsForHash().putAll(key, map);
redisTemplate.expire(key, TOKEN_VALID_DAYS, TimeUnit.DAYS);
}
@Override
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
String key = generateKey(seriesId);
List<String> hashKeys = new ArrayList<>();
hashKeys.add("username");
hashKeys.add("tokenValue");
hashKeys.add("date");
List<String> hashValues = redisTemplate.opsForHash().multiGet(key, hashKeys);
String username = hashValues.get(0);
String tokenValue = hashValues.get(1);
String date = hashValues.get(2);
if (null == username || null == tokenValue || null == date) {
return null;
}
Long timestamp = Long.valueOf(date);
Date time = new Date(timestamp);
PersistentRememberMeToken token = new PersistentRememberMeToken(username, seriesId, tokenValue, time);
return token;
}
@Override
public void removeUserTokens(String username) {
if (log.isDebugEnabled()) {
log.debug("token remove username: [{}]", username);
}
byte[] hashKey = redisTemplate.getHashKeySerializer().serialize("username");
RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
try (Cursor<byte[]> cursor = redisConnection.scan(ScanOptions.scanOptions().match(generateKey("*")).count(1024).build())) {
while (cursor.hasNext()) {
byte[] key = cursor.next();
byte[] hashValue = redisConnection.hGet(key, hashKey);
String storeName = (String) redisTemplate.getHashValueSerializer().deserialize(hashValue);
if (username.equals(storeName)) {
redisConnection.expire(key, 0L);
return;
}
}
} catch (IOException ex) {
log.warn("token remove exception", ex);
}
}
/**
* 生成key
*
* @param series
* @return
*/
private String generateKey(String series) {
return "spring:security:rememberMe:token:" + series;
}
}
到這里還是無法成功實(shí)現(xiàn)rememberme
繼續(xù)debug
發(fā)現(xiàn)驗(yàn)證cookie的時(shí)候使用的是
TokenBasedRememberMeServices
由于我們想要保存redis 在UserAuthenticationFilter中使用的是
PersistentTokenBasedRememberMeServices
生成cookie和驗(yàn)證的方法不一致
還需要加上配置
http
.rememberMe()
.rememberMeServices(rememberMeServices());
現(xiàn)在表單登錄可以正常使用rememberme了俯逾,
但是application/json類型的登錄請(qǐng)求還是沒有返回rememberme 的cookie
繼續(xù)debug
AbstractRememberMeServices中的 rememberMeRequested 方法使用
request.getParameter(parameter); 獲取的是表單參數(shù)
現(xiàn)在只能重寫這個(gè)方法了
MyPersistentTokenBasedRememberMeServices
public class MyPersistentTokenBasedRememberMeServices extends PersistentTokenBasedRememberMeServices {
public MyPersistentTokenBasedRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository);
}
@Override
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
boolean b = super.rememberMeRequested(request, parameter);
if (!b) {
String rememberMe = ApplicationJsonContextHolder.getString(parameter);
if (rememberMe != null &&
(rememberMe.equalsIgnoreCase("true") ||
rememberMe.equalsIgnoreCase("on") ||
rememberMe.equalsIgnoreCase("yes") ||
rememberMe.equals("1"))) {
return true;
}
}
return b;
}
}
把配置類中的remembermeservice替換了
@Bean
public RememberMeServices rememberMeServices() {
MyPersistentTokenBasedRememberMeServices rememberMeServices = new MyPersistentTokenBasedRememberMeServices(REMEMBER_ME, myUserDetailsService, myRedisTokenRepository);
rememberMeServices.setParameter(REMEMBER_ME);
rememberMeServices.setTokenValiditySeconds(3600);
return rememberMeServices;
}
因?yàn)橐鉀Qapplication/json復(fù)用的問題昧港,
新建了一個(gè)ApplicationJsonContextHolder類 深寥,
ApplicationJsonContextHolder
public class ApplicationJsonContextHolder {
private final static Logger log = LoggerFactory.getLogger(ApplicationJsonContextHolder.class);
private static ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal<>();
public static void reset() {
threadLocal.remove();
}
/**
* 獲取body參數(shù) body中的參數(shù)只能獲取一次
*
* @param request
* @return
*/
public static void init(HttpServletRequest request) {
Map<String, String> bodyParams = threadLocal.get();
if (bodyParams == null) {
ObjectMapper objectMapper = new ObjectMapper();
try (InputStream is = request.getInputStream()) {
bodyParams = objectMapper.readValue(is, Map.class);
} catch (IOException e) {
}
if (bodyParams == null) bodyParams = new HashMap<>();
threadLocal.set(bodyParams);
}
//log.info("json數(shù)據(jù)》》》》》》》》》》》》" + JSONObject.toJSONString(bodyParams));
}
public static Object get(String key) {
return threadLocal.get().get(key);
}
public static String getString(String key) {
return String.valueOf(get(key));
}
}
在過濾器中初始化這個(gè)ApplicationJsonContextHolder
@Component
public class ApplicationJsonFilter implements Filter {
private final static Logger log = LoggerFactory.getLogger(ApplicationJsonFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ApplicationJsonContextHolder.init((HttpServletRequest) servletRequest);
filterChain.doFilter(servletRequest, servletResponse);
/*用完要?jiǎng)h掉*/
ApplicationJsonContextHolder.reset();
}
}
配置過濾器
http.addFilterBefore(applicationJsonFilter, UsernamePasswordAuthenticationFilter.class);
到這里應(yīng)該就能同時(shí)支持表單請(qǐng)求和application/json請(qǐng)求的rememberme功能了
用了 threadlocal request中的輸入流沒有內(nèi)容了
最后還是使用了裝飾器模式
http://www.reibang.com/p/58c1afb8e6af