美文网首页
spring security 兼容表单和json请求+跨域+r

spring security 兼容表单和json请求+跨域+r

作者: 张建勇9511 | 来源:发表于2020-06-29 09:49 被阅读0次
image.png
image.png

跨域配置

 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("请求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参数  body中的参数只能获取一次
     * @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;
    }
}

创建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;
    }

到这里遇到一个坑

后面想要加入rememberme 功能 按照正常的配置方法是不行的
因为UsernamePasswordAuthenticationFilter已经被我们替换掉了
所以只能自己去配置userAuthenticationFilter的RememberMeServices

创建remembermeservice

@Bean
    public RememberMeServices rememberMeServices() {
        PersistentTokenBasedRememberMeServices rememberMeServices = new PersistentTokenBasedRememberMeServices(REMEMBER_ME, myUserDetailsService, myRedisTokenRepository);
        rememberMeServices.setParameter(REMEMBER_ME);
        rememberMeServices.setTokenValiditySeconds(3600);
        return rememberMeServices;
    }

redis存储实现类

@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;
    }
}

到这里还是无法成功实现rememberme
继续debug

发现验证cookie的时候使用的是
TokenBasedRememberMeServices
由于我们想要保存redis 在UserAuthenticationFilter中使用的是
PersistentTokenBasedRememberMeServices
生成cookie和验证的方法不一致

还需要加上配置

  http
                        .rememberMe()
                        .rememberMeServices(rememberMeServices());

现在表单登录可以正常使用rememberme了,
但是application/json类型的登录请求还是没有返回rememberme 的cookie

继续debug
AbstractRememberMeServices中的 rememberMeRequested 方法使用
request.getParameter(parameter); 获取的是表单参数
现在只能重写这个方法了

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;
    }

因为要解决application/json复用的问题,
新建了一个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参数  body中的参数只能获取一次
     *
     * @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数据》》》》》》》》》》》》" + 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));
    }
}

在过滤器中初始化这个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);
        /*用完要删掉*/
        ApplicationJsonContextHolder.reset();
    }
}

配置过滤器

 http.addFilterBefore(applicationJsonFilter, UsernamePasswordAuthenticationFilter.class);

到这里应该就能同时支持表单请求和application/json请求的rememberme功能了

用了 threadlocal request中的输入流没有内容了
最后还是使用了装饰器模式
https://www.jianshu.com/p/58c1afb8e6af

相关文章

  • spring security 兼容表单和json请求+跨域+r

    跨域配置 跨域filter类 自定义UserAuthenticationFilter 继承 UsernamePas...

  • 2018-12-11

    spring security 的跨域问题 spring security跨域设置 在spring-sercuri...

  • ajax(2)

    6.jsonp跨域请求 7、JSON的了解?XML和JSON的区别?

  • 跨域

    关于跨域大概可以分为 iframe 的跨域和纯粹的跨全域请求。 3种跨全域方法: 1、JSONP 全称:JSON ...

  • 跨域的解决方式与演示

    关于跨域大概可以分为 iframe 的跨域和纯粹的跨全域请求。 3种跨全域方法: 1、JSONP 全称:JSON ...

  • 跨域

    什么是跨域? 为什么会发生ajax跨域?浏览器限制跨域【发出的请求不是本域】XHR请求【json】 解决思路:1:...

  • javaweb 中的跨域请求

    方法一 、使用ajax进行跨域请求 方法json数据 配置拦截器用于允许指定的请求跨域 为含有/json/的url...

  • Ajax请求跨域问题

    遇到ajax请求跨域问题,解决方式,改dataType为jsonp json和jsonp返回数据格式json格式 ...

  • JSONP的劫持

    关于 JSONP JSONP 全称是 JSON with Padding ,是基于 JSON 格式的为解决跨域请求...

  • Zuul处理预检请求

    在spring cloud的Zuul服务网关的过滤器中,项目中使用JWT权限验证,前端JS为了兼容跨域请求,使用A...

网友评论

      本文标题:spring security 兼容表单和json请求+跨域+r

      本文链接:https://www.haomeiwen.com/subject/jqznfktx.html