在以前做东西的时候,对于认证鉴权的框架技术选型,通常使用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是由于我根本没写这个页面,但已经可以证明我们的配置生效,避免了硬编码的问题
在下一篇博客里,我会介绍自定义身份认证和一些处理器的用法及源码,敬请关注!