在以前做东西的时候,对于认证鉴权的框架技术选型,通常使用Apache Shiro,可能是接触比较早,感觉用起来比较方便的原因,知道最近接了一个好大好大的项目分布式应用+大数据数据库+私有IaaS云+PaaS,埋头苦学一星期,算是吃透了Spring Security的一半,那么一是为了记录在学习过程中的知识点,二是为了让没有接触过安全框架,或者还在使用数据库,自己写Filter比较原始方式做权限控制的朋友提供一个学习的参考。那么废话不说太多,下面开始一步一步来。
一、初识Spring Security
首先看名字就可以看出来,这个框架出自于Pivotal 的Spring系列。Spring Security 提供了基于javaEE的企业应用,全面的安全服务。 大家使用Spring Secruity的原因有很多,但是大部分都是发现了由于javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景所需的深度。 那么Security提供的 “认证”和“授权”(或者访问控制) 是整个框架也是本系列博客所要讲解的重要内容。
二、引入Security依赖
Security官方链接:https://projects.spring.io/spring-security/
打开你的IED,在Maven坐标如下:想使用其他版本请去如上链接,如果你之前接触过Spring IO那么无需指定Version.
<dependencies> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.0.5.RELEASE</version> </dependency> </dependencies>
Gradle如下:
dependencies { compile 'org.springframework.security:spring-security-web:5.0.5.RELEASE' }
三、Security的基本认证功能的使用
首先明确一个事情,Spring Security所有提供给开发者的认证和鉴权功能都是基于过滤器链的,而我看到的大部分讲解Security的博客和资料中,却很少提及每个功能作用的过滤器是谁?过滤器的执行流程是怎么样的!我之前在学习Docker和K8S的时候买的一本书讲解的非常透彻,作者不仅仅在讲怎么用!而是从:是什么?怎么用?为什么?这三方面来说,因此我会在文中通过Debug或流程图,为大家深入的讲解这款框架的东西。
3.1、HttpBasic认证
那么当你引入了Security的依赖后,无需做其他任何的事情,启动项目,访问的时候你就会发现你的应用已经有了一个基本的认证功能,其实前面说的话不是完全正确的,由于笔者之前使用的Spring Boot版本为1.5,依赖的Security 4.X版本,所以无需任何配置,启动项目访问则会弹出默认的httpbasic认证,那么本次Demo是基于security5.X ,Spring Boot2.X版本去做的,经过我翻阅官方文档和Spring Boot2.X的properties,在这个5.X的版本中已经将默认的认证方式更改为表单认证,并且security.basic.enabled属性同样是过期了的。
下面的图是官方给出的属性配置,已经无security.basic.enabled
在这里笔者十分负责任的说,如果想开启httpbasic认证,需要在配置类中进行配置才可以,亲测通过properties配置不生效,开启httpbasic认证的代码如下:
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter ; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http .httpBasic()//开启httpbasic认证 .and() .authorizeRequests()//Security表达式 .anyRequest().authenticated();//所有请求都需要认证 } }
启动项目,在启动的时候会发现控制台有如下输出,这个字符串就是Security默认生成的认证密码。
访问应用,弹出httpbasic,通过生成的默认密码进行认证,不过这种方式基本不太会使用,不着重介绍。
3.2、表单认证
表单认证是大部分业务场景中都需要使用的一种认证方式,那么如何配置,又有哪些可配置的呢?看如下代码:
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurer Adapter; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage("/loginPage.html")//用户未认证时,转跳到认证的页面 .loginProcessingUrl("/toLogin")//form中action的地址,也就是处理认证请求的URL .usernameParameter("UM")//form中用户名密码的name名 .passwordParameter("PW") .defaultSuccessUrl("/index")//认证成功后默认转跳的URL .and() .authorizeRequests() .antMatchers("/loginPage.html","/toLogin").permitAll()//最后不要忘记将自定义的 //如上不需要认证的URL加进来 .anyRequest().authenticated(); } }
我们访问任意需要认证的URL,发现会自动转跳到配置的认证页面。
我们暂时先使用内存用户,在properties中配置一个admin用户,模拟登录。
登陆后,自动转跳到index的controller中,对了!如果你使用了自己的登录页面,记得将CSRF防护功能关掉哦,否则你讲无法进行认证。
四、基于Spring的配置
那么通过以上代码的讲解,你应该对于认证功能的基本使用有了一定的了解,但是上面的代码中有一个开发界非常难以容忍的问题,就是含有大量的硬编码!!!那么我们如何解决的?
首先我们创建三个类,分别叫:SecurityProperties、ToLoginProperties、LoginProperties,代码分别如下:
public class LoginProperties { private String loginPage = "/loginPage.html"; private String loginProcessingUrl = "/toLogin"; private String usernameParameter = "UM"; private String passwordParameter = "PW"; private String defaultSuccessUrl = "/index"; public String getLoginPage() { return loginPage; } public void setLoginPage(String loginPage) { this.loginPage = loginPage; } public String getLoginProcessingUrl() { return loginProcessingUrl; } public void setLoginProcessingUrl(String loginProcessingUrl) { this.loginProcessingUrl = loginProcessingUrl; } public String getUsernameParameter() { return usernameParameter; } public void setUsernameParameter(String usernameParameter) { this.usernameParameter = usernameParameter; } public String getPasswordParameter() { return passwordParameter; } public void setPasswordParameter(String passwordParameter) { this.passwordParameter = passwordParameter; } public String getDefaultSuccessUrl() { return defaultSuccessUrl; } public void setDefaultSuccessUrl(String defaultSuccessUrl) { this.defaultSuccessUrl = defaultSuccessUrl; } }
public class ToLoginProperties { private LoginProperties loginProperties = new LoginProperties(); public LoginProperties getLoginProperties() { return loginProperties; } public void setLoginProperties(LoginProperties loginProperties) { this.loginProperties = loginProperties; } }
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "jy.security") public class SecurityProperties { private ToLoginProperties toLoginProperties = new ToLoginProperties(); public ToLoginProperties getToLoginProperties() { return toLoginProperties; } public void setToLoginProperties(ToLoginProperties toLoginProperties) { this.toLoginProperties = toLoginProperties; } }
通过以上代码,我们就可以读取到配置文件中,以“jy.security”开头的属性,并将属性对应的值注入到其中,这样当我们配置了对应属性的时候,值即被替换,未配置的时候则走我们的默认值。
然后我们把securityconfig类在修改一下,不在使用硬编码,而是将SecurityProperties类注入。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import com.spring.demo.peoperties.SecurityProperties; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private SecurityProperties securityProperties; @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage(securityProperties.getToLoginProperties().getLoginProperties().getLoginPage())//用户未认证时,转跳到认证的页面 .loginProcessingUrl(securityProperties.getToLoginProperties().getLoginProperties().getLoginProcessingUrl())//form中action的地址,也就是处理认证请求的URL .usernameParameter(securityProperties.getToLoginProperties().getLoginProperties().getUsernameParameter())//form中用户名密码的name名 .passwordParameter(securityProperties.getToLoginProperties().getLoginProperties().getPasswordParameter()) .defaultSuccessUrl(securityProperties.getToLoginProperties().getLoginProperties().getDefaultSuccessUrl())//认证成功后默认转跳的URL .and() .authorizeRequests() .antMatchers(securityProperties.getToLoginProperties().getLoginProperties().getLoginPage(), securityProperties.getToLoginProperties().getLoginProperties().getLoginProcessingUrl()).permitAll() .anyRequest().authenticated() .and() .csrf().disable() ; } }
最后我们做一个测试,证明通过配置文件的方式,可以对我们的配置类修改,我们修改未认证时转跳的认证页面的URL为“/loginTest”
重启项目,访问/index,结果如下:404是由于我根本没写这个页面,但已经可以证明我们的配置生效,避免了硬编码的问题
在下一篇博客里,我会介绍自定义身份认证和一些处理器的用法及源码,敬请关注!