一,Spring Security快速入门
1,什么是Spring Security
Spring Security是专门针对基于Spring的项目的安全框架,充分利用了依赖注入和AOP来实现安全的功能。
在早期的Spring Security版本,使用Spring Security需要使用大量的xml配置,而下面将全部基于Java配置来实现Spring Security的功能。
安全框架有两个重要的概念,即认证(Authentication)和授权(Authorization)。认证即确认用户可以访问当前系统;授权即确定用户在当前系统下所拥有的功能权限,下面将围绕认证和授权展开。
2,Spring Security的配置
1)DelegatingFilterProxy
Spring Security为我们提供了一个多个过滤器来实现所有安全的功能,我们只需注册一个特殊的DelegatingFilterProxy过滤器到WebAppliactionInitializer即可。
而在实际使用中,我们只需让自己的Initializer类继承AbstractSecurityWebApplicationInitializer抽象类即可。AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer接口,并通过onStartup方法调用:
insertSpringSecurityFilterChain(servletContext);
它为我们注册了DelegatingFilterProxy。insertSpringSecurityFilterChain源码如下:
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = "springSecurityFilterChain";
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
String contextAttribute = this.getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
this.registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
package com.jack.springboot12springsecurit.controller;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* create by jack 2017/10/12
*/
public class AppInitializer extends AbstractSecurityWebApplicationInitializer{
}
Spring Security的配置和Spring MVC的配置类似,只需在一个配置类上注解@EnableWebSecurity,并让这个类继承WebSecurityConfigurerAdapter即可。我们可以通过重写configure方法来配置相关的安全配置。代码如下:
package com.jack.springboot12springsecurit.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* create by jack 2017/10/12
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
}
3,用户认证
认证需要我们有一套用户数据的来源,而授权则是对于某个用户有相应的角色权限。在Spring Security里我们通过重写
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
1)内存中的用户
使用AuthenticationManagerBuilder的inMemoryAuthentication方法即可添加在内存中的用户,并可给用户指定角色权限。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
auth.inMemoryAuthentication()
.withUser("jack1").password("jack1").roles("ROLE_ADMIN")
.and()
.withUser("jack2").password("jack2").roles("ROLE_USER");
}
JDBC中的用户之间指定dataSource即可。
package com.jack.springboot12springsecurit.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import javax.sql.DataSource;
/**
* create by jack 2017/10/12
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
auth.jdbcAuthentication().dataSource(dataSource);
}
}
public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled from users where username = ?"; public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority from authorities where username = ?";
public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled from users where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority from authorities where username = ?";
当然我们可以自定义我们的查询用户和权限的sql语句,例如:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username,password,true "
+ "from myusers where username = ?")
.authoritiesByUsernameQuery("select username,role "
+" from roles where username = ?");
}
上面的两种用户和权限的获取方式只限于内存或者jdbc,我们的数据访问方式可以是各种各样的,可以是非关系型数据库,也可以是我们常用的JPA等。
这时我们需要自定义实现UserDetailsService接口。上面的内存中用户及JDBC用户就是UserDetailsService的实现,定义如下:
package com.jack.springboot12springsecurit.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.ArrayList;
import java.util.List;
/**
* create by jack 2017/10/12
*/
public class CustomUserService implements UserDetailsService{
@Autowired
SysUserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user= userRepository.findByUserName(username);
List authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new User(user.getUsername(),user.getPassword(),authorities);
}
}
除此之外,我们还需要注册这个CustomUserService,代码如下:
@Bean
UserDetailsService customUserService(){
return new CustomUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService());
}
4,请求授权
Spring Security是通过重写
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
Spring Security使用以下匹配器来匹配请求路径:
antMatchers:使用Ant风格的路径匹配。
regexMatchers:使用正则表达式匹配路径
anyRequest:匹配所有请求路径。
在匹配了请求路径后,需要针对当前用户的信息对请求路径进行安全处理,Spring Security提供了下面的安全处理方法:
access(String) :Spring EL表达式结构为true时可访问
anonymous():匿名可访问
denyAll():用户不能访问
fullyAuthenticated():用户完全认证可访问(非remember me下自动登入)
hasAnyAuthority(String...):如果用户有参数,则其中任一权限可访问
hasAnyRole(String...):如果用户有参数,则其中任一角色可访问
hasAuthority(String):如果用户有参数,则其权限可访问
hasIpAddress(String):如果用户来自参数中的ip则可访问
hasRole(String):若用户有参数中的角色可访问
permitAll():用户可任意访问
rememberMe():允许通过remember-me登录的用户访问
authenticated():用户登录后可访问
我们可以看一下下面的示例代码:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//通过authorizeRequests方法来开始请求权限配置
.antMatchers("/admin/**").hasRole("ROLE_ADMIN")//请求匹配/admin/**,只有ROLE_ADMIN角色的用户可访问
.antMatchers("/user/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")//请求匹配/user/**,拥有ROLE_ADMIN或ROLE_USER角色的用户都可访问
.anyRequest().authenticated();//其余所有的请求都需要认证后(登入后)才可访问
}
5,定制登入行为
我们也可以通过重写
protected void configure(HttpSecurity http)
方法来定制我们的登入行为。
下面将重用的登入行为的定制以简短的代码演示:
@Override
protected void configure(HttpSecurity http) throws Exception {
/*http
.authorizeRequests()//通过authorizeRequests方法来开始请求权限配置
.antMatchers("/admin/**").hasRole("ROLE_ADMIN")//请求匹配/admin/**,只有ROLE_ADMIN角色的用户可访问
.antMatchers("/user/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")//请求匹配/user/**,拥有ROLE_ADMIN或ROLE_USER角色的用户都可访问
.anyRequest().authenticated();//其余所有的请求都需要认证后(登入后)才可访问*/
http
.formLogin()//通过formLogin方法定制登录操作
.loginPage("/login")//使用loginPage方法定制登入页面的访问地址
.defaultSuccessUrl("/index")//defaultSuccessUrl指定登入成功后转向的页面
.failureUrl("/login?error")//failureUrl指定登入失败后转向的页面
.permitAll()
.and()
.rememberMe()//rememberMe开启cookie存储用户信息
.tokenValiditySeconds(1209600)//tokenValiditySeconds指定cookie有效期为1209600秒,即两个星期
.key("myKey")//指定cookie中的私钥
.and()
.logout()//使用logout方法定制注销行为
.logoutUrl("/custom-logout")//logoutUrl指定注销的URL路径
.logoutSuccessUrl("/logout-success")//logoutSuccessUrl指定注销成功后转向的页面
.permitAll();
}
Spring Boot针对Spring Security的自动配置在org.springframework.boot.autoconfigure.security包中。
主要通过SecurityAutoConfiguration和SecurityProperties来完成配置。
SecurityAutoConfiguration导入SpringBootWebSecurityConfiguration中配置。在SpringBootWebSecurityConfiguration配置中,我们获得如下的自动配置:
1)自动配置了一个内存中的用户,账号为user,密码在程序启动时出现
2)忽略/css/**,/js/**,/images/**和/**/favicon.ico等静态文件的拦截。
3)自动配置的securityFilterChainRegistration的Bean.
SecurityProperties使用“security”为前缀的属性配置Spring Security相关的配置,包含:
#内存中的用户默认账号为uer
security.user.name=user
#1默认用户的密码
security.user.password=
#默认用户的角色
security.user.role=USER
#是否需要ssl支持
security.require-ssl=false
#是否开启“跨战请求伪造”支持,默认关闭
security.enable-csrf=false
security.basic.enabled=true
security.basic.realm=Spring
# /**
security.basic.path=
security.basic.authorize-mode=
security.filter-order=0
security.headers.xss=true
security.headers.cache=true
security.headers.frame=true
security.headers.content-type=false
security.headers.hsts=all
security.sessions=stateless
#用逗号隔开的无须拦截的路径
security.ignored=
Spring Boot为我们做了如此多的配置,当我们需要自己扩展的配置时,只需配置类继承WebSecurityConfigurerAdapter类即可,无须使用@EnableWebSecurity注解,例如:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{}
三,实战
下面的示例中,演示使用Spring Boot下的Spring Security的配置,完成简单的认证授权的功能。下面我们将通过Spring Data JPA获得用户数据。页面模板使用Thymeleaf,Thymeleaf也为我们提供了支持Spring Security的标签。
1,新建Spring Boot项目
新建Spring Boot项目,依赖JPA,Security,Thymeleaf,mysql驱动
pom.xml代码如下:
4.0.0
com.jack
springboot12springsecurit
0.0.1-SNAPSHOT
jar
springboot12springsecurit
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.7.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-thymeleaf
org.thymeleaf.extras
thymeleaf-extras-springsecurity4
mysql
mysql-connector-java
com.alibaba
fastjson
1.2.39
org.springframework.boot
spring-boot-starter-test
test
org.springframework.security
spring-security-test
test
org.springframework.boot
spring-boot-maven-plugin
application.properties如下:
server.port=9090
spring.datasource.data-username=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/jack
spring.datasource.username=root
spring.datasource.password=root
logging.level.org.springframework.security=INFO
spring.thymeleaf.cache=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
将bootstrap.min.css放置在src/main/resources/static/css下,此路径默认不拦截
2,用户和角色
我们使用JPA来定义用户和角色
用户:
package com.jack.springboot12springsecurit.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* create by jack 2017/10/18
*/
/**
* 让我们的用户实体实现UserDetailsService接口,我们的用户实体即为Spring Security所使用的用户
*/
@Entity
public class SysUser implements UserDetails{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue//(strategy= GenerationType.AUTO)
private Integer id;
private String username;
private String password;
/**
* 配置用户和角色的多对多关心
*/
@ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)
private List roles;
public static long getSerialVersionUID() {
return serialVersionUID;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public void setUsername(String username) {
this.username = username;
}
/**
* 重写getAuthorities方法,将用户的角色作为权限
* @return
*/
@Override
public Collection getAuthorities() {
List auths = new ArrayList<>();
List roles = this.roles;
for (SysRole role:
roles) {
auths.add(new SimpleGrantedAuthority(role.getName()));
}
return auths;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List getRoles() {
return roles;
}
public void setRoles(List roles) {
this.roles = roles;
}
}
角色:
package com.jack.springboot12springsecurit.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* create by jack 2017/10/18
*/
@Entity
public class SysRole {
@Id
@GeneratedValue//(strategy= GenerationType.AUTO)
private Integer id;
//角色名称
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
当我们配置用户和角色的多对多关心后,通过设置:
spring.jpa.hibernate.ddl-auto=update
为我们自动生成用户表:sys_user,角色表:sys_role,关联表:sys_user_roles.
针对上面的结构,我们初始化一些数据来方便我们演示。在src/main/resources下,新建data.sql,即新建两个用户,角色分别为role_admin和role_user,代码如下:
INSERT INTO sys_user(id,username,password) VALUES (1,'jack1','jack1');
INSERT INTO sys_user(id,username,password) VALUES (2,'jack2','jack2');
INSERT INTO sys_role(id,name) VALUES (1,'ROLE_ADMIN');
INSERT INTO sys_role(id,name) VALUES (2,'ROLE_USER');
INSERT INTO sys_user_roles(sys_user_id,roles_id) VALUES (1,1);
INSERT INTO sys_user_roles(sys_user_id,roles_id) VALUES (2,2);
用来测试不同角色用户的数据展示:、
package com.jack.springboot12springsecurit.pojo;
/**
* create by jack 2017/10/18
*/
public class Msg {
private String title;
private String content;
private String etraInfo;
public Msg(String title, String content, String etraInfo) {
super();
this.title = title;
this.content = content;
this.etraInfo = etraInfo;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getEtraInfo() {
return etraInfo;
}
public void setEtraInfo(String etraInfo) {
this.etraInfo = etraInfo;
}
}
我们这里的数据访问很简单,代码如下:
package com.jack.springboot12springsecurit.dao;
import com.jack.springboot12springsecurit.entity.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* create by jack 2017/10/18
*/
public interface SysUserRepository extends JpaRepository {
SysUser findByUsername(String username);
}
4,自定义UserDetailsService
package com.jack.springboot12springsecurit.service;
import com.jack.springboot12springsecurit.dao.SysUserRepository;
import com.jack.springboot12springsecurit.entity.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* create by jack 2017/10/18
*/
/**
* 自定义需实现UserDetailsService接口
*/
public class CustomUserService implements UserDetailsService{
@Autowired
private SysUserRepository userRepository;
/**
* 重写loadUserByUsername方法获得用户
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
//我们当前的用户实现了UserDetails接口,可直接返回给Spring Security使用
return user;
}
}
5,配置
1)Spring MVC配置
package com.jack.springboot12springsecurit.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* create by jack 2017/10/18
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers(registry);
registry.addViewController("/login").setViewName("login");
}
}
注册访问/login转向login.html页面
2)Spring Security配置
package com.jack.springboot12springsecurit.config;
import com.jack.springboot12springsecurit.service.CustomUserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* create by jack 2017/10/18
*/
/**
* 扩展Spring Security配置需要继承WebSecurityConfigurerAdapter
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
/**
* 注册CustomUserService的Bean
* @return
*/
@Bean
UserDetailsService customUserService(){
return new CustomUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
/**
* 添加我们自定义的user detail service认证
*/
auth.userDetailsService(customUserService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
http.authorizeRequests()
.anyRequest()
.authenticated()//所有请求需要认证即登入后才能访问
.and()
.formLogin()
.loginPage("/login")
.successForwardUrl("/security/index")//登入成功后的跳转路径
.failureUrl("/login?error")
.permitAll()//定制登入行为,登入页面可任意访问
.and()
.logout()
.permitAll();//定制注销行为,注销请求可任意访问
}
}
1)登录页面login.html,代码如下:
security登入页面
已成功注销
有错误,请重试
使用账户密码登入
无更多信息显示
此控制器很简单,只为首页显示准备数据:
package com.jack.springboot12springsecurit.controller;
import com.jack.springboot12springsecurit.pojo.Msg;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* create by jack 2017/10/18
*/
@Controller
@RequestMapping("security")
public class SecurityController {
@RequestMapping("/index")
//@RequestMapping("/")
public String index(Model model){
Msg msg = new Msg("测试标题", "测试内容", "额外信息,只对管理员显示");
model.addAttribute("msg", msg);
return "home";
}
}
1)登入。访问http://localhost:9090/login,将会自动转到登录页面http://localhost:9090/login,如下:
使用正确的账号密码登录:
1,使用role_admin的用户进行登录:
使用错误的账号登录:
2)注销。登入成功后,点击注销按钮如下:
3)用户信息
页面上我们将用户名显示在页面的标题上,如下:
用jack1和jack2用户名登入,对应的角色不同,上面显示的视图也不一样。
源代码地址:https://github.com/wj903829182/SpringCloudTwo/tree/master/springboot12springsecurit