1. 导读
1.读完这篇文章你将免费获得一个SpringSecurity测试demo项目源码(可直接运行)。
2.你将学到如何快速创建一个SpringSecurity项目,实现简单的登入功能,很方便的集成到Springboot项目。
3.查看demo项目的运行测试效果,更具体地了解SpringSecurity功能。。
2. 快速认识Spring Security
什么是Spring Security
Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。它支持众多的认证技术,如LDAP、基于IEFT RFC 的标准、Form-based authentication(简单用户接口)等等。概括就是:控制系统的登入拦截、用户登入认证、用户登入token的鉴权、系统api接口等资源的简单用户接口的权限控制。
简单地说,就是系统的登入逻辑和代码实现不用自己开发,使用Spring Security进行配置化开发就可以。
比较Spring Security登入业务与原生登入业务
1)编写登入页面的form代码。
2)编写登入controller接口代码。
3)根据传入的用户名参数从数据库查询用户信息。
4)比较查询出来的密码数据与传入的密码,相同则登入通过,否则登入失败。
Spring Security登入逻辑开发流程:
1)编写登入页面的form代码。
2)Spring Security核心配置类开发。
3)实现UserDetailsService接口,在loadUserByUsername方法实现用户信息的加载。
为什么SpringSecurity登入业务开发工作量少
使用SpringSecurity来做系统的安全服务到底有多爽,下图代表了我此刻的内心。
快速入门
SpringBoot项目pom依赖
这个是集成SpringSecurity所需要依赖的jar包。
<!--security start--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
Springboot项目的pom文件中至少要包含上面两个依赖包。
SpringSecurity配置类
配置类要继承抽象类WebSecurityConfigurerAdapter,并使用注解@Configuration、@EnableGlobalMethodSecurity(prePostEnabled = true)修饰配置类。
/** * @Author: Galen * @Date: 2019/3/27-14:43 * @Description: spring-security权限管理的核心配置 **/ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserSecurityService userSecurityService; /** * @Author: jackdking * @Description: 配置userDetails的数据源,密码加密格式 * @Date: 2020/5/21-5:24 * @Param: [auth] * @return: void **/ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userSecurityService) .passwordEncoder(new BCryptPasswordEncoder());// 实现自定义登录校验 } /** * @Author: Galen * @Description: 配置放行的资源 * @Date: 2019/3/28-9:23 * @Param: [web] * @return: void **/ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/login.html", "/index.html","/css/**", "/static/**", "/login_p", "/favicon.ico") // 给 swagger 放行;不需要权限能访问的资源 .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui", "/configuration/security"); } /** * @Author: Galen * @Description: 拦截配置 * @Date: 2019/4/4-10:44 * @Param: [http] * @return: void **/ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 使其支持跨域 .requestMatchers(CorsUtils :: isPreFlightRequest).permitAll() // 其他路径需要授权访问 .anyRequest().authenticated() .and() .formLogin().loginPage("/login_p")// 设置登录页面, //奇怪的是 这个url并没有被访问到。 .loginProcessingUrl("/login")// 自定义的登录接口,这个接口必须和你登入页面form的action地址一样。否则一直进入login_p页面:如果loginProcessingUrl不配置,默认是跟loginPage一样 .usernameParameter("username").passwordParameter("password")//告诉security form表单用户名和密码的参数表达式。 .failureHandler(new MyAuthenticationFailureHandler())// 这个失败处理 跟 failureUrl 配置是互斥的。 两种只选择一种 .successHandler(new MyAuthenticationSuccessHandler())//跟defaultSuccessUrl互斥 ,这个支持返回json数据。如果不设置这个成功后的处理器,则会报错 999。 // .defaultSuccessUrl("/index") //跟successHandler配置互斥,这个支持重定向到成功页面。 访问指定页面,用户未登入,跳转至登入页面,如果登入成功,跳转至用户访问指定页面,用户访问登入页面,默认的跳转页面 // .failureUrl("/error_p") // 重定向失败页面 跟 failureHandler 配置是互斥的。 两种只选择一种 .permitAll() .and() .csrf().disable(); //关闭csrf } }
- 配置类覆盖configure(AuthenticationManagerBuilder auth),设置用户信息来源接口userDetailsService的实现类,以及用户密码的加密实现对象BCryptPasswordEncoder。
- 配置类覆盖configure(WebSecurity web),告知security框架哪些url不登入也能访问,例如一些css文件、js文件、登入页面等等。
- 配置类覆盖configure(HttpSecurity http),告知security登入业务的一些信息,例如登录页面、自定义的登录接口、form表单用户名和密码的参数表达式、登入失败或成功的handler类。
userDetailsService的开发
在这个实现类中,security框架允许开发者自定义用户信息的来源:可以是在代码里面,或是数据库、缓存服务器、第三方服务等。在这个demo项目里,我们运用的比较简单,直接写死在代码中,创建了SecuritySysUser对象返回给Security。
SecuritySysUser securityUser = new SecuritySysUser(); securityUser.setUsername(username); securityUser.setPassword("$2a$10$4H1JSQxyrJlguu0/V4DnR.s2NBjE.k6rI6.W.1AFL0UEnR2IR2/5y"); securityUser.setEnabled(true); List<SecuritySysRole> roles = new ArrayList<>(); SecuritySysRole role = new SecuritySysRole(); role.setNameCn("ROLE_ADMIN"); securityUser.setRoles(roles); if (securityUser == null) { throw new UsernameNotFoundException("用户名不对"); } log.info("用户信息:{}" , securityUser.toString()); return securityUser;
到这,使用security框架来开发用户登入业务结束了,那我们来看看运行效果。
3. 运行Spring Security项目
本文的demo项目获取方式在文章底部查看。
右击项目文件com.jackdking.security.SpringSecurityApplication,选择Run As -> Java Application。
浏览器输入http://localhost:9090,进入login登入页面。
输入用户名/密码:admin/admin,点击登入按钮,然后查看返回的登入json信息。
登入成功返回的json数据是security框架的登入成功处理器返回的结果:successHandler(new MyAuthenticationSuccessHandler())。
@Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); RespBean respBean = RespBean.ok("【MyAuthenticationSuccessHandler】登录成功!", SecurityUserUtil.getCurrentUser()); new GalenWebMvcWrite().writeToWeb(request,response, respBean); System.out.println("【MyAuthenticationSuccessHandler】登录成功!"); }
输入错误的用户名和密码,返回的json信息,是错误处理器中的第一个异常错误类UsernameNotFoundException。
登入失败返回的json数据是security框架的登入失败处理器返回的结果:failureHandler(new MyAuthenticationFailureHandler())。
response.setContentType("application/json;charset=utf-8"); RespBean respBean; if (exception instanceof BadCredentialsException || exception instanceof UsernameNotFoundException) { respBean = RespBean.error("账户名或者密码输入错误!"); } else if (exception instanceof LockedException) { respBean = RespBean.error("账户被锁定,请联系管理员!"); } else if (exception instanceof CredentialsExpiredException) { respBean = RespBean.error("密码过期,请联系管理员!"); } else if (exception instanceof AccountExpiredException) { respBean = RespBean.error("账户过期,请联系管理员!"); } else if (exception instanceof DisabledException) { respBean = RespBean.error("账户被禁用,请联系管理员!"); } else { respBean = RespBean.error("登录失败!"); } System.out.println("失败逻辑处理。"); //response.setStatus(401); new GalenWebMvcWrite().writeToWeb(request, response, respBean);
4. 如何正确看待web安全服务
作为架构师,当然希望系统能使用成熟的、功能特性丰富的安全服务框架,而security正是做好的选择。而且无论是从建设安全服务的人力、测试、运维、风险等成本角度考虑,security都是非常香的。
Spring Security安全服务框架的配置化开发也大大提高了整个团队的开发效率,如果系统单独自己做一个安全服务框架,未来团队成员交接成本也会非常的高,也会伴随着较大的风险成本。
查看更多 “Java架构师方案” 系列文章 以及 SpringBoot2.0学习示例
完整的demo项目,请关注公众号“前沿科技bot“并发送"SEC-ONE"获取。