spring security token认证_快速实现图形验证码、token验证

本文介绍如何结合Spring Security快速实现Google图形验证码和Token的安全性校验。涉及UserDetailsService接口、AuthenticationFailureHandler、AuthenticationSuccessHandler以及WebSecurityConfigurerAdapter类的使用,详细讲解了用户信息Service、权限认证处理器、过滤器的构建,以及验证码配置和案例实践。
摘要由CSDN通过智能技术生成

验证码(图形、短信、邮箱)、token机制对于系统的安全性已经是老生常谈;

本文将结合spring-security快速实现Google图形验证码、token的安全性校验。

技术储备

1、UserDetailsService接口

/** * Core interface which loads user-specific data. * 

* It is used throughout the framework as a user DAO and is the strategy used by the * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider * DaoAuthenticationProvider}. * *

* The interface requires only one read-only method, which simplifies support for new * data-access strategies. * * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider * @see UserDetails * * @author Ben Alex */public interface UserDetailsService {/** * Locates the user based on the username. In the actual implementation, the search * may possibly be case sensitive, or case insensitive depending on how the * implementation instance is configured. In this case, the UserDetails * object that comes back may have a username that is of a different case than what * was actually requested.. * * @param username the username identifying the user whose data is required. * * @return a fully populated user record (never null) * * @throws UsernameNotFoundException if the user could not be found or the user has no * GrantedAuthority */UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}

该接口位于org.springframework.security.core.userdetails

主要作用:加载用户信息的核心接口;被用于DaoAuthenticationProvider的策略

2、AuthenticationFailureHandler、AuthenticationSuccessHandler接口

/** * Strategy used to handle a failed authentication attempt. * 

* Typical behaviour might be to redirect the user to the authentication page (in the case * of a form login) to allow them to try again. More sophisticated logic might be * implemented depending on the type of the exception. For example, a * {@link CredentialsExpiredException} might cause a redirect to a web controller which * allowed the user to change their password. * * @author Luke Taylor * @since 3.0 */public interface AuthenticationFailureHandler {/** * Called when an authentication attempt fails. * @param request the request during which the authentication attempt occurred. * @param response the response. * @param exception the exception which was thrown to reject the authentication * request. */void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response, AuthenticationException exception)throws IOException, ServletException;}

/** * Strategy used to handle a successful user authentication. * 

* Implementations can do whatever they want but typical behaviour would be to control the * navigation to the subsequent destination (using a redirect or a forward). For example, * after a user has logged in by submitting a login form, the application needs to decide * where they should be redirected to afterwards (see * {@link AbstractAuthenticationProcessingFilter} and subclasses). Other logic may also be * included if required. * * @author Luke Taylor * @since 3.0 */public interface AuthenticationSuccessHandler {/** * Called when a user has been successfully authenticated. * * @param request the request which caused the successful authentication * @param response the response * @param authentication the Authentication object which was created during * the authentication process. */void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication)throws IOException, ServletException;}

接口位于org.springframework.security.web.authentication;

AuthenticationFailureHandler 用于处理失败的身份验证尝试的策略;

AuthenticationSuccessHandler 当用户成功通过身份验证时调用。

3、WebSecurityConfigurerAdapter 类

@Order(100)public abstract class WebSecurityConfigurerAdapter implementsWebSecurityConfigurer {private final Log logger = LogFactory.getLog(WebSecurityConfigurerAdapter.class);private ApplicationContext context;private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy();private ObjectPostProcessor objectPostProcessor = new ObjectPostProcessor() {public  T postProcess(T object) {throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @EnableWebSecurity and @Configuration");}};private AuthenticationConfiguration authenticationConfiguration;private AuthenticationManagerBuilder authenticationBuilder;private AuthenticationManagerBuilder localConfigureAuthenticationBldr;private boolean disableLocalConfigureAuthenticationBldr;private boolean authenticationManagerInitialized;private AuthenticationManager authenticationManager;private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();private HttpSecurity http;private boolean disableDefaults;}

该类位于org.springframework.security.config.annotation.web.configuration

作用:为权限配置类,该类还实现了WebSecurityConfigurer接口;用户必须创建一个新类来继承AbstractHttpConfigurer。

我们将会用到的方法有:

1、protected void configure(AuthenticationManagerBuilder auth)

用户自定义注册权限,我们将使用我们自己的用户体系来重写。即,使用用户名+密码方式

auth.userDetailsService(customerUserDetailService).passwordEncoder(passwordEncoder());

2、protected void configure(HttpSecurity http)

配置Http权限,其中有`http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic()`配置

3、配置上面的权限认证成功、失败处理器以及登出处理器

  @Bean  public AuthenticationSuccessHandler authenticationSuccessHandler(){    return new CustomAuthenticationSuccessHandler();  }  @Bean  public AuthenticationFailureHandler authenticationFailureHandler(){    return new CustomAuthenticationFailHandler();  }  @Bean  public LogoutHandler logoutHandler(){    return new CustomLogoutSuccessHandler();  }

技术实现

1、构建用户信息Service,实现userDetailsService

目的是:用系统的用户体系构建权限的控制;即,用户名+密码

@Componentpublic class CustomerUserDetailService implements UserDetailsService {  @Resource  private SysUserMapper sysUserMapper;  @Override  public UserDetails loadUserByUsername(String s){    SysUser sysUser = this.selectByUserName(s);    if(ObjectUtil.isNull(sysUser)){      throw new CustomAuthenticationException("用户不存在");    }    return this.getDetail(sysUser);  }  private UserDetails getDetail(SysUser sysUser){    return new CustomUserDetailsUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), Lists.newArrayList());  }  public UserDetails loadUserByUserId(Long id){    SysUser sysUser = this.selectById(id);    if(ObjectUtil.isNull(sysUser)){      throw new CustomAuthenticationException("用户不存在");    }    return this.getDetail(sysUser);  }}

2、分别构建权限认证成功处理器、失败处理器、登出处理器

用户名+密码匹配成功,我们将会登陆的用户信息进行token处理,下次客户端只需要透传token即可,不需要任何的登陆用户信息,信息更安全,可靠。

@Slf4j@Componentpublic class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {  @Resource  private StringRedisTemplate stringRedisTemplate;  private ObjectMapper objectMapper = new ObjectMapper();  @SneakyThrows  @Override  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {    String token;    Long userId = 0L;    if (authentication.getPrincipal() instanceof CustomUserDetailsUser) {      CustomUserDetailsUser userDetailsUser = (CustomUserDetailsUser) authentication.getPrincipal();      //用户名+时间      token = SecureUtil.md5(userDetailsUser.getUsername() + System.currentTimeMillis());      userId = userDetailsUser.getUserId();    } else {      token = SecureUtil.md5(String.valueOf(System.currentTimeMillis()));    }    stringRedisTemplate.opsForValue().set(Constants.AUTHENTICATION_TOKEN + token, token, Constants.TOKEN_EXPIRE, TimeUnit.SECONDS);    //返回前端的Token,V为用户的ID    stringRedisTemplate.opsForValue().set(token, Long.toString(userId), Constants.TOKEN_EXPIRE, TimeUnit.SECONDS);    response.setCharacterEncoding(CharsetUtil.UTF_8);    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);    PrintWriter printWriter = response.getWriter();    Map dataMap = Maps.newLinkedHashMap();    dataMap.put(Constants.TOKEN, token);    printWriter.append(objectMapper.writeValueAsString(ResultVo.success(dataMap)));  }}

权限认证失败处理器

@Slf4j@Componentpublic class CustomAuthenticationFailHandler implements AuthenticationFailureHandler {  private ObjectMapper objectMapper = new ObjectMapper();  @SneakyThrows  @Override  public void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response, AuthenticationException exception){    response.setCharacterEncoding(CharsetUtil.UTF_8);    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);    PrintWriter printWriter = response.getWriter();    printWriter.append(objectMapper.writeValueAsString(ResultVo.fail(exception.getMessage())));  }}

3、分别构建图形验证码、token的过滤器

a271538597ff3a0d84265f2c0fe61b7b.gif

图形验证码过滤器

统一过滤请求的URL,如果登陆API,需要校验图形验证码。验证码校验失败,将无权限操作,交给权限认证失败处理器处理。

@Slf4jpublic class AuthenticationTokenFilter extends BasicAuthenticationFilter {  private StringRedisTemplate stringRedisTemplate;  private CustomerUserDetailService customerUserDetailService;  private ObjectMapper objectMapper = new ObjectMapper();  public AuthenticationTokenFilter(AuthenticationManager authenticationManager, StringRedisTemplate template, CustomerUserDetailService customUserDetailsService) {    super(authenticationManager);    this.stringRedisTemplate = template;    this.customerUserDetailService = customUserDetailsService;  }  @Override  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {    String token = request.getHeader(Constants.TOKEN);    //如果存在Token、对Token进行校验-这个Token对应用户信息    if (!Strings.isNullOrEmpty(token)) {      String userId = stringRedisTemplate.opsForValue().get(token);      if (ObjectUtil.isNull(userId)) {        writer(response, "无效token");        return;      }      UserDetails userDetails = customerUserDetailService.loadUserByUserId(Long.valueOf(userId));      UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));      SecurityContextHolder.getContext().setAuthentication(authentication);    }    chain.doFilter(request, response);  }  @SneakyThrows  public void writer(HttpServletResponse response, String msg) {    response.setContentType("application/json;charset=UTF-8");    response.setStatus(HttpServletResponse.SC_OK);    response.getWriter()        .write(objectMapper.writeValueAsString(ResultVo.fail(HttpServletResponse.SC_UNAUTHORIZED, msg)));  }
bddb8fbafe907620420e34950898e416.png

token过滤器

将需要对token进行有效性的验证

@Slf4jpublic class AuthenticationTokenFilter extends BasicAuthenticationFilter {  private StringRedisTemplate stringRedisTemplate;  private CustomerUserDetailService customerUserDetailService;  private ObjectMapper objectMapper = new ObjectMapper();  public AuthenticationTokenFilter(AuthenticationManager authenticationManager, StringRedisTemplate template, CustomerUserDetailService customUserDetailsService) {    super(authenticationManager);    this.stringRedisTemplate = template;    this.customerUserDetailService = customUserDetailsService;  }  @Override  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {    String token = request.getHeader(Constants.TOKEN);    //如果存在Token、对Token进行校验-这个Token对应用户信息    if (!Strings.isNullOrEmpty(token)) {      String userId = stringRedisTemplate.opsForValue().get(token);      if (ObjectUtil.isNull(userId)) {        writer(response, "无效token");        return;      }      UserDetails userDetails = customerUserDetailService.loadUserByUserId(Long.valueOf(userId));      UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));      SecurityContextHolder.getContext().setAuthentication(authentication);    }    chain.doFilter(request, response);  }  @SneakyThrows  public void writer(HttpServletResponse response, String msg) {    response.setContentType("application/json;charset=UTF-8");    response.setStatus(HttpServletResponse.SC_OK);    response.getWriter()        .write(objectMapper.writeValueAsString(ResultVo.fail(HttpServletResponse.SC_UNAUTHORIZED, msg)));  }

4、Google图形验证码配置类

加载图形验证码的bean

@Configurationpublic class KaptchaConfig {  @Bean  public DefaultKaptcha producer() {    Properties properties = new Properties();    properties.put("kaptcha.border", "no");    properties.put("kaptcha.textproducer.font.color", "black");    properties.put("kaptcha.textproducer.char.space", "5");    Config config = new Config(properties);    DefaultKaptcha defaultKaptcha = new DefaultKaptcha();    defaultKaptcha.setConfig(config);    return defaultKaptcha;  }}

5、案例实现

新建登陆API、验证码获取API

注入图形验证码的bean,写图片验证码

登陆接口无需任何操作,已经在权限认证成功的时候,生成了对应的token

@RestController@Slf4jpublic class LoginController {  @Resource  private Producer producer;  @Resource  private StringRedisTemplate stringRedisTemplate;    @SneakyThrows  @RequestMapping("/sys/code/{randomStr}")  public void captcha(@PathVariable("randomStr") String randomStr, HttpServletResponse response) {    response.setHeader("Cache-Control", "no-store, no-cache");    response.setContentType("image/jpeg");    String text = producer.createText();    log.info("【验证码生成成功】randomStr:{},captcha:{}", randomStr, text);    BufferedImage image = producer.createImage(text);    String redisKey = Constants.IMG_NUMBER_CODE_KEY + randomStr;    stringRedisTemplate.opsForValue().set(redisKey, text, Constants.TOKEN_EXPIRE, TimeUnit.SECONDS);    ServletOutputStream out = response.getOutputStream();    ImageIO.write(image, "jpg", out);    IOUtils.closeQuietly(out);  }    @PostMapping("/token/login")  @OperationLog(value = "用户登陆",type = LogOperationEnum.OTHER)  public ResultVo> login() {    return ResultVo.success();  }}

用户管理API

如果没有AuthIgnore方法注解,则都需要开启token验证。

@RestController@RequestMapping("/user")public class UserController {  @Resource  private SysUserService sysUserService;    @AuthIgnore  @OperationLog(value = "新增用户",type = LogOperationEnum.ADD)  @PostMapping("/add")  public ResultVo register(@RequestBody SysUser vo){    return ResultVo.success(sysUserService.add(vo));  }  @GetMapping("/info")  @SysLog(value = "用户基本信息")  public ResultVo info(){    return ResultVo.success(sysUserService.info());  }}

完。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值