当前位置: 首页 > 后端技术 > Java

SpringSecurity如火如荼!DIY登陆的两种方式

时间:2023-04-01 14:52:54 Java

@[toc]一般我们在使用SpringSecurity的时候都是使用SpringSecurity自带的登陆方案,配置登陆界面,配置登陆参数,然后配置登陆回调就可以了会用到,这个用法也算是最佳实践了!但!总会有一些稀奇古怪的需求,比如自定义登录,像Shiro那样写登录逻辑,如果要实现的话,怎么实现呢?宋哥今天就来给大家分享一下。宋哥想了想,想在SpringSecurity中自定义登录逻辑。我们有两个想法,但是这两个想法的底层实现其实是一样的,我们来看一下。1.化腐朽为神奇。之前松哥给大家分享了一个SpringSecurity的视频:你没见过的精彩登录。这个视频主要是给大家分享一下我们其实可以使用HttpServletRequest来完成系统登录。这实际上是JavaEE规范,这种登录方式虽然冷门,但是很好玩!然后松哥也给大家分享了一个视频:SpringSecurity登录数据的获取这个视频的最后一讲其实是SpringSecurity对HttpServletRequest登录逻辑的实现,或者说HttpServletRequest中提供的登录相关API,SpringSecurity对其进行了重写根据自己的实现。有了这两个保留知识,第一个DIYSpringSecurity登录方案就呼之欲出了。1.1实践下面来看看具体的操作。首先我们创建一个SpringBoot工程,引入Web和Security两个依赖,如下:为了方便,我们在application.properties中配置默认??的用户名和密码:spring.security.user.name=javaboyspring.security.user。password=123接下来,我们提供一个SecurityConfig来允许登录接口:@ConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().csrf().disable();}}登录接口为/login,后面我们的自定义登录逻辑会写在这里面,我们来看看:@RestControllerpublicclassLoginController{@PostMapping("/login")publicStringlogin(Stringusername,Stringpassword,HttpServletRequestreq){try{req.login(用户名,密码);返回“成功”;}catch(ServletExceptione){e.printStackTrace();}返回“失败”;}}直接调用HttpServletRequest#login方法,传入用户名和密码完成登录操作,最后我们提供一个测试接口,如下:@RestControllerpublicclassHelloController{@GetMapping("/hello")publicStringhello(){return"hello安全!”;}}只是这个!启动项目,我们先访问/hello接口,访问失败。接下来我们访问/login接口进行登录操作,如下:登录成功后,再访问/hello接口,访问成功。这不是很容易吗?登录成功后,授权等后续操作保持不变。1.2原理分析上面的登录方式的原理其实是宋大哥一开始介绍的。不熟悉的可以看这两个视频了解一下:奇怪的登录SpringSecurity登录数据获取上一讲这里我也多说几句。我们在LoginController#login方法中获取到的HttpServletRequest实例,其实是HttpServlet3RequestFactory中一个内部类Servlet3SecurityContextHolderAwareRequestWrapper的对象。在这个类中重写了HttpServletRequest的login和authenticate方法。先看一下登录方法,如下:@Overridepublicvoidlogin(Stringusername,Stringpassword)throwsServletException{if(isAuthenticated()){thrownewServletException("Cannotperformloginfor'"+username+"'已经验证为'"+getRemoteUser()+"'");}AuthenticationManagerauthManager=HttpServlet3RequestFactory.this.authenticationManager;if(authManager==null){HttpServlet3RequestFactory.this.logger.debug("authenticationManager为null,因此允许原始HttpServletRequest处理);password);return;}Authenticationauthentication=getAuthentication(authManager,username,password);SecurityContextHolder.getContext().setAuthentication(authentication);}可以看到如果用户已经通过认证,会抛出异常获取一个AuthenticationManager对象。调用getAuthentication方法完成登录。该方法中会根据用户名和密码构造UsernamePasswordAuthenticationToken对象,然后调用Authentication#authenticate方法完成登录。具体代码如下:privateAuthenticationgetAuthentication(AuthenticationManagerauthManager,Stringusername,Stringpassword)throwsServletException{try{returnauthManager.authenticate(newUsernamePasswordAuthenticationToken(username,password));}catch(AuthenticationExceptionex){SecurityContextHolder.clearContext();抛出新的ServletException(ex.getMessage(),ex);}}这个方法在Authentication对象之后返回一个authentication。最后,将经过身份验证的Authentication对象存储在SecurityContextHolder中。具体逻辑这里就不赘述了。我在之前的公众号【南方小雨】的视频中已经讲过很多次了。这就是登录方法的执行逻辑。Servlet3SecurityContextHolderAwareRequestWrapper类也重写了HttpServletRequest#authenticate方法,这个也是做认证的方法:@Overridepublicbooleanauthenticate(HttpServletResponseresponse)throwsIOException,ServletException{AuthenticationEntryPointentryPoint=HttpServlet3RequestFactory;if(entryPoint==null){HttpServlet3RequestFactory.this.logger.debug("authenticationEntryPoint为空,因此允许原始HttpServletRequest处理身份验证");返回super.authenticate(响应);}如果(isAuthenticated()){返回真;}entryPoint.commence(this,response,newAuthenticationCredentialsNotFoundException("UserisnotAuthenticated"));returnfalse;}可以看到,该方法用于判断用户是否完成了认证操作。返回true表示用户已经完成认证,返回false表示用户还没有完成认证工作。2、源码的强大看了上面的原理分析,你应该也明白了第二种方案,即不使用HttpServletRequest#login方法,而是直接调用AuthenticationManager进行登录验证。一起来看看吧。首先,我们修改配置类如下:).anyRequest().authenticated().and().csrf().disable();}@Override@BeanpublicAuthenticationManagerauthenticationManagerBean()throwsException{DaoAuthenticationProviderprovider=newDaoAuthenticationProvider();InMemoryUserDetailsManagermanager=newInMemoryUserDetails(InMemoryUserDetailsManager)createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());provider.setUserDetailsS??ervice(经理);返回新的ProviderManager(提供者);}}首先在登录发布中,添加/login2接口,这是我要自定义的第二个登录接口。提供AuthenticationManager的实例。关于AuthenticationManager的玩法,松哥在之前的SpringSecurity系列中分享过多次,这里不再赘述(没看过的朋友公众号后台回复ss)。创建AuthenticationManager实例时,还需要提供一个DaoAuthenticationProvider。大家知道,用户密码的校验是在这个类中完成的,为DaoAuthenticationProvider配置了一个UserDetailsS??ervice实例,提供用户数据源。接下来,提供一个登录接口:@RestControllerpublicclassLoginController{@AutowiredAuthenticationManagerauthenticationManager;@PostMapping("/login2")publicStringlogin2(Stringusername,Stringpassword,HttpServletRequestreq){try{Authenticationtoken=authenticationManager.authenticate(newUsernamePasswordAuth(username,password));SecurityContextHolder.getContext().setAuthentication(令牌);返回“成功”;}catch(Exceptione){e.printStackTrace();}返回“失败”;}}在登录界面,传入用户名、密码等参数,然后将用户名、密码等参数封装成一个UsernamePasswordAuthenticationToken对象,最后调用AuthenticationManager#authenticate方法进行验证。验证成功后会返回一个经过验证的Authentication对象,然后手动将Authentication对象存放到SecurityContextHolder中。配置完成后,重启项目,进行登录测试。方案二和方案一类似,方案二其实就是把方案一的底层拉出来自己重新实现,仅此而已。3.总结好了,今天给大家介绍两种SpringSecurityDIY登录方案。这些方案在工作中可能不常用,但是对于大家理解SpringSecurity的原理还是有很大帮助的。有兴趣的朋友可以点击试一试~另外,如果觉得本文阅读有难度,不妨在公众号后台回复ss,阅读SpringSecurity系列其他文章,对你有帮助理解这篇文章。当然你也可以看看松哥的新书:本书已经由清华大学出版社正式出版。有兴趣的可以点这里->->>深入浅出地讲解SpringSecurity,一本书学会SpringSecurity。