SpringSecurity与JWT整合

一.首先用户登录成功之后关联jwt,并返回jwt
1.实现AuthenticationSuccessHandler
2.在AuthenticationSuccessHandler的handle方法中生成令牌并且把user信息写入jwt中,写入缓存以供前端传过来时通过令牌获取用户(这里不使用前端传过来的jwt里的用户信息,仅仅是用令牌来比对是否已存在该令牌并通过缓存获取用户和权限),这里最好给个有效时间,然后把jwt放入header中返回给前端,以后前端就用它来调用接口
代码:
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

//private final RequestCache requestCache = new HttpSessionRequestCache();
private final ObjectMapper jacksonObjectMapper;
private final RedisTemplate<String,Object> redisTemplate;

public JwtAuthenticationSuccessHandler(ObjectMapper jacksonObjectMapper, RedisTemplate<String, Object> redisTemplate) {
    this.jacksonObjectMapper = jacksonObjectMapper;
    this.redisTemplate = redisTemplate;
}

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
    log.info("JwtAuthenticationSuccessHandler=success");
    clearAuthenticationAttributes(request);
    handle(response,authentication);
}

protected final void clearAuthenticationAttributes(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session == null) return;
    session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}

protected void handle(HttpServletResponse response, Authentication authentication) throws IOException {
    if (response.isCommitted()) {
        log.debug("Response has already been committed.");
        return;
    }
    User sysUser=(User)authentication.getPrincipal();
    sysUser.setClazz(authentication.getClass());
    //AuthenticationAdapter authenticationAdapter=AuthenticationAdapter.authentication2AuthenticationAdapter(authentication);
    String authOfjson=jacksonObjectMapper.writeValueAsString(sysUser);
    String subject= UUID.randomUUID().toString();
    String authOfjwt= JWTHS256.buildJWT(subject,authOfjson);
    response.addHeader("jwt",authOfjwt);
    //跨域时允许header携带jwt
    response.addHeader("Access-Control-Expose-Headers" ,"jwt");
    redisTemplate.boundValueOps(SecurityConstants.getJwtKey(subject)).set("w",60, TimeUnit.MINUTES);
    response.setContentType("application/json;charset=utf-8");
    PrintWriter out = response.getWriter();

    User returnSysUser=new User();
    returnSysUser
            .setName(sysUser.getName())
            .setCurrentOrg(sysUser.getCurrentOrg())
            .setOrgIdMapRoleList(sysUser.getOrgIdMapRoleList());

    out.write(jacksonObjectMapper.writeValueAsString(new ReturnObject<>(this.getClass().getName(),ConstantOfReturnCode.GLOBAL_RESULT_SUCESS,"登录成功",returnSysUser)));
    out.flush();
    out.close();
}

}
3.在SpringSecurity的配置中注入:
http
.formLogin()
.successHandler(new JwtAuthenticationSuccessHandler(jacksonObjectMapper, redisTemplate))
这样调用成功了就会走这边处理
二.接下来就是前端传到后台时怎么处理,有两种方式,基本原理都是:在request.getHeader中获取jwt的令牌,然后再通过令牌从缓存中获取jwt),然后从缓存里得来的jwt里获取用户和权限,以此通过判断用户权限来控制用户对接口的使用
1.过滤器,在SpringSecurity的任意一个过滤器都可以
2.使用SpringSecurity的http.setSharedObject(SecurityContextRepository.class, new JwtSecurityContextRepository(redisTemplate, jacksonObjectMapper));
就是把SecurityContextRepository设成可共享的对象
代码:
package com.lc.yangzi.security.component.jwt;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lc.yangzi.security.component.SecurityConstants;
import com.lc.yangzi.security.domain.User;
import com.lc.yangzi.security.utility.JWTHS256;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
//import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
public class JwtSecurityContextRepository implements SecurityContextRepository {
protected final Log logger = LogFactory.getLog(this.getClass());

private final RedisTemplate<String,Object> redisTemplate;
private final ObjectMapper jacksonObjectMapper;

public JwtSecurityContextRepository(RedisTemplate<String, Object> redisTemplate, ObjectMapper jacksonObjectMapper) {
    this.redisTemplate = redisTemplate;
    this.jacksonObjectMapper = jacksonObjectMapper;
}

@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
    HttpServletRequest request = requestResponseHolder.getRequest();
    return readSecurityContextFromJWT(request);
}

@Override
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
}

@Override
public boolean containsContext(HttpServletRequest request) {
    return false;
}

private SecurityContext readSecurityContextFromJWT(HttpServletRequest request) {
    SecurityContext context=generateNewContext();
    String authenticationOfjwt=request.getHeader("jwt");
    if(StringUtils.isNotBlank(authenticationOfjwt)){
        try{
            Map<String,Object> map= JWTHS256.vaildToken(authenticationOfjwt);
            if(Objects.nonNull(map)&&map.size()==2){
                String subject=(String)map.get("subject");
                Boolean isExp=redisTemplate.hasKey(SecurityConstants.getJwtKey(subject));
                if(Objects.nonNull(isExp)&&isExp){//redis key 未过期
                    redisTemplate.expire(SecurityConstants.getJwtKey(subject),60, TimeUnit.MINUTES);//延期
                    String obj=(String)map.get("claim");
                    //AuthenticationAdapter authenticationAdapter=jacksonObjectMapper.readValue(obj, new TypeReference<>(){});
                    //Authentication authentication=AuthenticationAdapter.authenticationAdapter2Authentication(authenticationAdapter);
                    //Authentication authentication=jacksonObjectMapper.readValue(obj, new TypeReference<>(){});
                    //Authentication authentication=jacksonObjectMapper.readValue(obj,Authentication.class);
                    User sysUser=jacksonObjectMapper.readValue(obj, new TypeReference<User>(){});
                    Authentication authentication=new UsernamePasswordAuthenticationToken(sysUser,null,sysUser.getAuthorities());
                    context.setAuthentication(authentication);
                    //if(obj instanceof Authentication){
                        //context.setAuthentication((Authentication)obj);
                    //}else log.error("jwt包含authentication的数据非法");
                }else log.error("jwt数据过期");
            }else log.error("jwt数据非法");
        }catch (Exception e){
            e.printStackTrace();
            logger.error(e.getLocalizedMessage());
        }
    }else{
        if (logger.isDebugEnabled()) {
            logger.debug("No JWT was available from the HttpServletRequestHeader!");
        }
    }
    return context;
}
protected SecurityContext generateNewContext() {
    return SecurityContextHolder.createEmptyContext();
}

}
三.这里有个小知识点:
1.以上SpringSecurity的配置和初始化里有个适配器和关键,相当于一个过滤器,
2.这里有个对适配器的解释:
Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带。在常见的View(List View,Grid View)等地方都需要用到Adapter。
3.也就是说每次前端请求后端都要通过适配器,每次都会调用一边
代码
@Configuration
@Order(2)
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception {
//源码:SecurityContextConfigurer
//源码:SessionManagementConfigurer
http
.requestMatchers().antMatchers("/**")
.and()
.anonymous().disable()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest()
.authenticated()
.withObjectPostProcessor(new ObjectPostProcessor() {
public O postProcess(O fsi) {
//fsi.setRejectPublicInvocations(true); // 拒绝公共URL调用.源码:AbstractSecurityInterceptor,当请求的URL没有配置或者没有对应到角色时,不给予放行抛出异常!
fsi.setAccessDecisionManager(customAccessDecisionManager());
//使用默认的DefaultFilterInvocationSecurityMetadataSource及数据库配置的CustomSecurityMetadataSource
fsi.setSecurityMetadataSource(customSecurityMetadataSource(securityService, fsi.getSecurityMetadataSource()));
return fsi;
}
});

        http
                .addFilterBefore(
                        new KaptchaAuthenticationFilter(customAuthenticationFailureHandler(jacksonObjectMapper), securityConfigurator, redisTemplate),
                        UsernamePasswordAuthenticationFilter.class);
        http
                .formLogin()
                .loginProcessingUrl(securityConfigurator.getLoginProcessingUrl())
                .usernameParameter(securityConfigurator.getUsernameParameter())
                .passwordParameter(securityConfigurator.getPasswordParameter())
                .permitAll()
                .failureHandler(customAuthenticationFailureHandler(jacksonObjectMapper))
                .successHandler(new JwtAuthenticationSuccessHandler(jacksonObjectMapper, redisTemplate))
                .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
                    public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O upaf) {
                        //设置登录请求匹配,默认的路径为配置的路基,默认的方法为Post,详见:AbstractAuthenticationProcessingFilter及其子类:UsernamePasswordAuthenticationFilter
                        upaf.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(securityConfigurator.getLoginProcessingUrl()));
                        //UsernamePasswordAuthenticationFilter类的attemptAuthentication方法
                        upaf.setPostOnly(false);
                        return upaf;
                    }
                });
        http
                .exceptionHandling()
                .accessDeniedHandler(customAccessDeniedHandler(jacksonObjectMapper))
                .authenticationEntryPoint(customAuthenticationEntryPoint(jacksonObjectMapper));
        http
                .logout()
                .logoutUrl(securityConfigurator.getLogoutUrl())
                .logoutSuccessHandler((request, response, authentication) -> {
                    ReturnObject<Object> returnObject;
                    String authenticationOfjwt = request.getHeader("jwt");
                    if (StringUtils.isNotBlank(authenticationOfjwt)) {
                        try {
                            Map<String, Object> map = JWTHS256.vaildToken(authenticationOfjwt);
                            if (Objects.nonNull(map) && map.size() == 2) {
                                String subject = (String) map.get("subject");
                                Boolean isExp = redisTemplate.hasKey(SecurityConstants.getJwtKey(subject));
                                if (Objects.nonNull(isExp) && isExp) {
                                    returnObject = new ReturnObject<>(ConstantOfReturnCode.GLOBAL_RESULT_SUCESS, "退出成功");
                                    redisTemplate.delete(SecurityConstants.getJwtKey(subject));
                                } else {
                                    returnObject = new ReturnObject<>(ConstantOfReturnCode.GLOBAL_RESULT_FALSE, "退出失败", "jwt数据过期");
                                    log.error("jwt数据过期");
                                }
                            } else {
                                returnObject = new ReturnObject<>(ConstantOfReturnCode.GLOBAL_RESULT_FALSE, "退出失败", "jwt数据非法");
                                log.error("jwt数据非法");
                            }
                        } catch (Exception e) {
                            returnObject = new ReturnObject<>(ConstantOfReturnCode.GLOBAL_RESULT_FALSE, "退出失败", e.getLocalizedMessage());
                            log.error(e.getLocalizedMessage());
                        }
                    } else {
                        returnObject = new ReturnObject<>(ConstantOfReturnCode.GLOBAL_RESULT_FALSE, "退出失败", "jwt数据缺失");
                    }
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    out.write(jacksonObjectMapper.writeValueAsString(returnObject));
                    out.flush();
                    out.close();
                })
                .clearAuthentication(true)
                .permitAll();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//关闭Session
        http.setSharedObject(SecurityContextRepository.class, new JwtSecurityContextRepository(redisTemplate, jacksonObjectMapper));
        http.csrf().disable();
        http.cors().configurationSource(CorsConfigurationSource());
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值