spring boot整合spring security

-笔者在实际生产中,遇到需求:

  • 给公司的5个系统统一加上token效验的需求,做成公共的jar包,
  • 而且要求使用spring security,暂时不做url权限的控制
  • 所有的请求进入到不同的系统,系统可以自由配置那些请求需要拦截,那些不需要拦截,
  • 并且请求中的token在失效前都必须在统一登录系统(LDAP)刷新token,登录系统的token时长是2小时,其他系统统一设置成1小时,
    **-
  • 首先是对spring security进行一个大致的了解
    spring security包含大量的过滤器,抽象到晦涩难懂,核心配置类是WebSecurityConfigurerAdapter ,必须继承这个类,然后重写他的方法
    **
@Configuration
@EnableWebSecurity
public class TokenAuthConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().requestMatchers(“**”);// 这个是你自己自定义 那些不需要拦截,不需要token
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http
                .exceptionHandling()
                .defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), “url”) // ** 这个是 你自己配置那些需要进行验证token
                .and()
                .authenticationProvider(provider) // 这个provider就是你自定义提供的对token内对应的userinfo进行校验
                .addFilterBefore(“filter”, AnonymousAuthenticationFilter.class)// 自定义的需要的拦截器
                .authorizeRequests()
                .requestMatchers(“url”)// ** 这个是自定义的需要拦截的url 
                .authenticated()
                .and()
                .csrf().disable() // 禁用跨站拦截
                .formLogin().disable()// 不使用 自带的登录界面
                .httpBasic().disable()
                .logout().disable();
    }
    }
  • 自定义的provider:

这里使用了自定义的 本地缓存策略,就是在一个类里面写一个全局的map,然后设置一下过期时间,过期就进行清除淘汰,然后还使用了 一个自定义的restTemplate


@Slf4j
final class TokenAuthProvider extends AbstractUserDetailsAuthenticationProvider {

    @Value("${csf.request.appId}")
    private String appId;

    @Value("${csf.request.clientId}")
    private String clientId;

    @Value("${csf.request.clientSecret}")
    private String clientSecret;

    @Value("${csf.request.url}")
    private String url;

    @Resource
    private LocalCache localCache;

    @Resource
    private TokenRestTemplate tokenRestTemplate;

    @Override
    protected void additionalAuthenticationChecks(final UserDetails d, final UsernamePasswordAuthenticationToken auth) {
    }

    @Override
    protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
        logger.info("AbstractUserDetailsAuthenticationProvider retrieveUser ");
        final Object token = authentication.getCredentials();
        String userName=null;
        // 如果还处于本地有效期内,则不进行刷新
        if(localCache.hasKey(String.valueOf(token))){
            logger.info(" localCache  ");
            userName=(String)localCache.get(String.valueOf(token));
        }else{
            ResponseEntity<TokenInfoDto>  resultRespo=null;
            try {
                resultRespo= tokenRestTemplate.refresh(String.valueOf(token),url,appId,clientId,clientSecret);
            }catch (Exception ex){
                logger.error("验证token出错",ex);
                throw  new UsernameNotFoundException("Cannot find user with authentication token=" + token);
            }
            if(resultRespo==null||(resultRespo.getStatusCodeValue()!= HttpServletResponse.SC_OK)){
                throw  new UsernameNotFoundException("Cannot find user with authentication token=" + token);
            }
            userName=resultRespo.getBody().getUser().getUsername();
            localCache.set(String.valueOf(token),userName,1*60);// 本地缓存60分钟
        }
        UserDetails userDetails=new User(userName);
        logger.info("AbstractUserDetailsAuthenticationProvider  success");
        return userDetails;
    }

}
  • 自定义的内存—缓存策略
    首先使用map作为缓存容器,你就要考虑会不会内存溢出,所以给每一个值都设置一个过期时间,并且对数量进行控制是很有必要的,

  • 针对oom: 利用 软引用(接近oom 时,进行强制淘汰)+延时队列delayqueue去主动删除

  • 针对数量主动删除值:LinkedList 去主动淘汰存储最久的值
    自定义一个缓存对象 必须带有过期时间

public class DelayedCacheObject implements Delayed {

    private final String key; // 缓存的key
    private final SoftReference<Object> reference;// 软引用包装存储对象
    private final long expiryTime; // 缓存时间

    public DelayedCacheObject(String key, SoftReference<Object> reference, long expiryTime) {
        this.key = key;
        this.reference = reference;
        this.expiryTime = expiryTime;
    }

    public String getKey() {
        return key;
    }

    public SoftReference<Object> getReference() {
        return reference;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(expiryTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return Long.compare(expiryTime, ((DelayedCacheObject) o).expiryTime);
    }

}

自定义缓存容器操作类
1.虽然使用了 软引用和 延时队列来保证不会oom ,但是如果同时登陆的人超过几千,对机器的考验还是会很大,所以这里还加上一个 长度限制,
2.构造方法中 主动构造一个守护线程进行垃圾的回收-------一直对延时队列进行判断,是否有失效的

public class LocalCache  implements ICache {

    private final ConcurrentHashMap<String, SoftReference<Object>> CACHE_MAP = new ConcurrentHashMap<>();// 存储对象的缓存容器

    private final DelayQueue<DelayedCacheObject>    CLEANING_QUEUE  = new DelayQueue<>();// 强制GC的延时队列

    /**
     * 这个记录了缓存使用的最后一次的记录,最近使用的在最前面
     */
    private static final List<String> CACHE_USE_LOG_LIST = new LinkedList<>();
    /**
     * 缓存最大个数
     */
    private static final Integer CACHE_MAX_NUMBER = 1000;

    public LocalCache() {
        //将回收过期的key的线程设置为守护线程
        Thread cleanerThread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    DelayedCacheObject delayedCacheObject = CLEANING_QUEUE.take();
                    if(CACHE_MAP.containsKey(delayedCacheObject.getKey())){
                        CACHE_MAP.remove(delayedCacheObject.getKey(), delayedCacheObject.getReference());
                    }
                    if(CACHE_USE_LOG_LIST.contains(delayedCacheObject.getKey())){
                        CACHE_USE_LOG_LIST.remove(delayedCacheObject.getKey());
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        cleanerThread.setDaemon(true);
        cleanerThread.start();
    }

    @Override
    public void set(String key, Object value, int periodInMinute) {
        if (key == null) {
            return;
        }
        if (value == null) {
            CACHE_MAP.remove(key);
            CACHE_USE_LOG_LIST.remove(key);
        } else {
            checkSize();// 先检查已经缓存的数量
            saveCacheUseLog(key);
            long expiryTime = System.currentTimeMillis() + periodInMinute*60*1000L;
            SoftReference<Object> reference = new SoftReference<>(value);
            CACHE_MAP.put(key, reference);
            CLEANING_QUEUE.put(new DelayedCacheObject(key, reference, expiryTime));
        }
    }

    @Override
    public boolean hasKey(String key) {
        return CACHE_MAP.containsKey(key);
    }

    @Override
    public void remove(String key) {
        CACHE_MAP.remove(key);
        CACHE_USE_LOG_LIST.remove(key);
    }

    @Override
    public Object get(String key) {
        return Optional.ofNullable(CACHE_MAP.get(key)).map(SoftReference::get).orElse(null);
    }

    /**
     * 检查大小
     * 当当前大小如果已经达到最大大小
     * 首先删除过期缓存,如果过期缓存删除过后还是达到最大缓存数目
     * 删除最久未使用缓存
     */
    private  void checkSize() {
        if (CACHE_USE_LOG_LIST.size()>= CACHE_MAX_NUMBER) {
            String cacheKey = null;
            synchronized (CACHE_USE_LOG_LIST) {
                cacheKey = CACHE_USE_LOG_LIST.remove(CACHE_USE_LOG_LIST.size() - 1);
            }
            if (cacheKey != null) {
                remove(cacheKey);
            }
        }
    }

    /**
     * 保存缓存的使用记录
     */
    private  void saveCacheUseLog(String cacheKey) {
        synchronized (CACHE_USE_LOG_LIST) {
            CACHE_USE_LOG_LIST.remove(cacheKey);
            CACHE_USE_LOG_LIST.add(0,cacheKey);
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值