摘要:准备工作基本的配置就不说了,网上一堆例子,只要弄到普通的表单登录和自定义就可以。是基于的,因此才能在基于前起作用。这样我们没有破坏原有的获取流程,还是可以重用父类原有的方法来处理表单登录。
spring security用了也有一段时间了,弄过异步和多数据源登录,也看过一点源码,最近弄rest,然后顺便搭oauth2,前端用json来登录,没想到spring security默认居然不能获取request中的json数据,谷歌一波后只在stackoverflow找到一个回答比较靠谱,还是得要重写filter,于是在这里填一波坑。
准备工作基本的spring security配置就不说了,网上一堆例子,只要弄到普通的表单登录和自定义UserDetailsService就可以。因为需要重写Filter,所以需要对spring security的工作流程有一定的了解,这里简单说一下spring security的原理。
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. *重写UsernamePasswordAnthenticationFilter* 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
* * @param request so that request attributes can be retrieved * * @return the password that will be presented in theAuthenticationDao
will need to * generate the expected password in a corresponding manner. *Authentication
* request token to theAuthenticationManager
*/ 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 theAuthentication
* request token to theAuthenticationManager
*/ protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); }
上面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(); authRequest = 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里面。
@Override protected void configure(HttpSecurity http) throws Exception { http .cors().and() .antMatcher("/**").authorizeRequests() .antMatchers("/", "/login**").permitAll() .anyRequest().authenticated() //这里必须要写formLogin(),不然原有的UsernamePasswordAuthenticationFilter不会出现,也就无法配置我们重新的UsernamePasswordAuthenticationFilter .and().formLogin().loginPage("/") .and().csrf().disable(); //用重写的Filter替换掉原有的UsernamePasswordAuthenticationFilter http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } //注册自定义的UsernamePasswordAuthenticationFilter @Bean CustomAuthenticationFilter customAuthenticationFilter() throws Exception { CustomAuthenticationFilter filter = new CustomAuthenticationFilter(); filter.setAuthenticationSuccessHandler(new SuccessHandler()); filter.setAuthenticationFailureHandler(new FailureHandler()); filter.setFilterProcessesUrl("/login/self"); //这句很关键,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己组装AuthenticationManager filter.setAuthenticationManager(authenticationManagerBean()); return filter; }
题外话,如果搭自己的oauth2的server,需要让spring security oauth2共享同一个AuthenticationManager(源码的解释是这样写可以暴露出这个AuthenticationManager,也就是注册到spring ioc)
@Override @Bean // share AuthenticationManager for web and oauth public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
至此,spring security就支持表单登录和异步json登录了。
参考来源stackoverflow的问答
其它链接我的简书
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67520.html
摘要:实现用户认证本次,我们通过的授权机制,实现用户授权。启用注解默认的是不进行授权注解拦截的,添加注解以启用注解的全局方法拦截。角色该角色对应菜单示例用户授权代码体现授权思路遍历当前用户的菜单,根据菜单中对应的角色名进行授权。 引言 上一次,使用Spring Security与Angular实现了用户认证。Spring Security and Angular 实现用户认证 本次,我们通过...
摘要:发现无效后,会返回一个的访问拒绝,不过可以通过配置类处理异常来定制行为。恶意用户可能提交一个有效的文件,并使用它执行攻击。默认是禁止进行嗅探的。 前言 xss攻击(跨站脚本攻击):攻击者在页面里插入恶意脚本代码,用户浏览该页面时,脚本代码就会执行,达到攻击者的目的。原理就是:攻击者对含有漏洞的服务器注入恶意代码,引诱用户浏览受到攻击的服务器,并打开相关页面,执行恶意代码。xss攻击方式...
摘要:框架具有轻便,开源的优点,所以本译见构建用户管理微服务五使用令牌和来实现身份验证往期译见系列文章在账号分享中持续连载,敬请查看在往期译见系列的文章中,我们已经建立了业务逻辑数据访问层和前端控制器但是忽略了对身份进行验证。 重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API 重拾后端之Spring Boot(一):REST API的搭建...
摘要:在整个学习过程中,我最关心的内容有号几点,其中一点是前后端分离的情况下如何不跳转页面而是返回需要的返回值。登录成功,不跳转页面,返回自定义返回值在官方文档第节,有这么一段描述要进一步控制目标,可以使用属性作为的替代。 在整个学习过程中,我最关心的内容有号几点,其中一点是【前后端分离的情况下如何不跳转页面而是返回需要的返回值】。下面就说一下学习结果,以xml配置位李。 登录成功,不跳转页...
摘要:创建应用有很多方法去创建项目,官方也推荐用在线项目创建工具可以方便选择你要用的组件,命令行工具当然也可以。对于开发人员最大的好处在于可以对应用进行自动配置。 使用JWT保护你的Spring Boot应用 - Spring Security实战 作者 freewolf 原创文章转载请标明出处 关键词 Spring Boot、OAuth 2.0、JWT、Spring Security、SS...
阅读 682·2021-11-18 10:02
阅读 3518·2021-09-02 10:21
阅读 1706·2021-08-27 16:16
阅读 2036·2019-08-30 15:56
阅读 2332·2019-08-29 16:53
阅读 1354·2019-08-29 11:18
阅读 2880·2019-08-26 10:33
阅读 2621·2019-08-23 18:34