JSON

详解Spring Security如何配置JSON登录

字号+ 作者:H5之家 来源:H5之家 2017-10-01 08:04 我要评论( )

这篇文章主要介绍了详解Spring Security如何配置JSON登录,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

时间:2017-09-12来源: 作者:源码库 文章热度: ℃

spring security用了也有一段时间了,弄过异步和多数据源登录,也看过一点源码,最近弄rest,然后顺便搭oauth2,前端用json来登录,没想到spring security默认居然不能获取request中的json数据,谷歌一波后只在stackoverflow找到一个回答比较靠谱,还是得要重写filter,于是在这里填一波坑。

准备工作

基本的spring security配置就不说了,网上一堆例子,只要弄到普通的表单登录和自定义UserDetailsService就可以。因为需要重写Filter,所以需要对spring security的工作流程有一定的了解,这里简单说一下spring security的原理。

Spring,Security,JSON登录,JSON

spring security 是基于javax.servlet.Filter的,因此才能在spring mvc(DispatcherServlet基于Servlet)前起作用。

  • UsernamePasswordAuthenticationFilter:实现Filter接口,负责拦截登录处理的url,帐号和密码会在这里获取,然后封装成Authentication交给AuthenticationManager进行认证工作
  • Authentication:贯穿整个认证过程,封装了认证的用户名,密码和权限角色等信息,接口有一个boolean isAuthenticated()方法来决定该Authentication认证成功没;
  • AuthenticationManager:认证管理器,但本身并不做认证工作,只是做个管理者的角色。例如默认实现ProviderManager会持有一个AuthenticationProvider数组,把认证工作交给这些AuthenticationProvider,直到有一个AuthenticationProvider完成了认证工作。
  • AuthenticationProvider:认证提供者,默认实现,也是最常使用的是DaoAuthenticationProvider。我们在配置时一般重写一个UserDetailsService来从数据库获取正确的用户名密码,其实就是配置了DaoAuthenticationProvider的UserDetailsService属性,DaoAuthenticationProvider会做帐号和密码的比对,如果正常就返回给AuthenticationManager一个验证成功的Authentication
  • 看UsernamePasswordAuthenticationFilter源码里的obtainUsername和obtainPassword方法只是简单地调用request.getParameter方法,因此如果用json发送用户名和密码会导致DaoAuthenticationProvider检查密码时为空,抛出BadCredentialsException。

    /** * Enables subclasses to override the composition of the password, such as by * including additional values and a separator. * <p> * This might be used for example if a postcode/zipcode was required in addition to * the password. A delimiter such as a pipe (|) should be used to separate the * password and extended value(s). The <code>AuthenticationDao</code> will need to * generate the expected password in a corresponding manner. * </p> * * @param request so that request attributes can be retrieved * * @return the password that will be presented in the <code>Authentication</code> * request token to the <code>AuthenticationManager</code> */ protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } /** * Enables subclasses to override the composition of the username, such as by * including additional values and a separator. * * @param request so that request attributes can be retrieved * * @return the username that will be presented in the <code>Authentication</code> * request token to the <code>AuthenticationManager</code> */ protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); }

    重写UsernamePasswordAnthenticationFilter

    上面UsernamePasswordAnthenticationFilter的obtainUsername和obtainPassword方法的注释已经说了,可以让子类来自定义用户名和密码的获取工作。但是我们不打算重写这两个方法,而是重写它们的调用者attemptAuthentication方法,因为json反序列化毕竟有一定消耗,不会反序列化两次,只需要在重写的attemptAuthentication方法中检查是否json登录,然后直接反序列化返回Authentication对象即可。这样我们没有破坏原有的获取流程,还是可以重用父类原有的attemptAuthentication方法来处理表单登录。

    /** * AuthenticationFilter that supports rest login(json login) and form login. * @author chenhuanming */ public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //attempt Authentication when Content-Type is json if(request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) ||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){ //use jackson to deserialize json ObjectMapper mapper = new ObjectMapper(); UsernamePasswordAuthenticationToken authRequest = null; try (InputStream is = request.getInputStream()){ AuthenticationBean authenticationBean = mapper.readValue(is,AuthenticationBean.class); authRequest = new UsernamePasswordAuthenticationToken( authenticationBean.getUsername(), authenticationBean.getPassword()); }catch (IOException e) { e.printStackTrace(); new UsernamePasswordAuthenticationToken( "", ""); }finally { setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } } //transmit it to UsernamePasswordAuthenticationFilter else { return super.attemptAuthentication(request, response); } } }

    封装的AuthenticationBean类,用了lombok简化代码(lombok帮我们写getter和setter方法而已)

    @Getter @Setter public class AuthenticationBean { private String username; private String password; }

    WebSecurityConfigurerAdapter配置

    重写Filter不是问题,主要是怎么把这个Filter加到spring security的众多filter里面。

     

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    相关文章
    • 关于Springboot与Thymeleaf模板引擎整合教程

      关于Springboot与Thymeleaf模板引擎整合教程

      2017-09-20 10:34

    • spring boot 学习笔记

      spring boot 学习笔记

      2017-09-19 12:23

    • 超详细 Spring @RequestMapping 注解使用技巧

      超详细 Spring @RequestMapping 注解使用技巧

      2017-09-17 08:00

    • springmvc学习(小知识点整理)

      springmvc学习(小知识点整理)

      2017-07-24 18:00

    网友点评
    c