Java架构师方案——Spring Security(一)快速入门(附完整项目代码)
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开发工作少,是因为它对登入业务进行了非常完整的建模,登入业务的整个过程中,需要开发者自己开发的局部逻辑不多,其中包括:自定义登入页面,自定义用户数据获取来源(UserDetailsService实现类)
使用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安全服务框架的配置化开发也大大提高了整个团队的开发效率,如果系统单独自己做一个安全服务框架,未来团队成员交接成本也会非常的高,也会伴随着较大的风险成本。
完整的demo项目,请关注公众号“前沿科技bot“并发送"SEC-ONE"获取。
- 本文标签: Java架构师方案 Spring Security Spring Boot
- 版权声明: 本站原创文章,于2020年11月11日由空白发布,转载请注明出处