SpringSecurity介绍
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
应用级别的安全主要分为“验证( authentication) ”和“(授权) authorization ”两个部分。这也是Spring Security主要需要处理的两个部分。“ Authentication ”指的是建立规则( principal )的过程。规则可以是一个用户、设备、或者其他可以在我们的应用中执行某种操作的其他系统。” Authorization “指的是判断某个 principal 在我们的应用是否允许执行某个操作。在 进行授权判断之前,要求其所要使用到的规则必须在验证过程中已经建立好了。
除了验证机制, Spring Security 也提供了一系列的授权能力。主要感兴趣的是以下三个方面:
- 对web请求进行授权
- 授权某个方法是否可以被调用
- 授权访问单个领域对象实例
关于SpringSecurity的学习主要是参考http://www.tianshouzhi.com/api/tutorials/spring_security_4/250,这里面还提供了对源码的解读,对于我这种渣渣来说,看起来还是有点费劲的
在Springsecurity中有两种配置方式,是xml文件配置和java配置,两者配置差别不打,个人感觉java配置使用起来比较方便,所以主要记录一下java配置的过程。
java基本配置
新建项目
新建一个SpringBoot项目,pom.xml中需要的包(勾选或者直接手写)
devtools
thymeleaf
security
在pom中添加这三个包之后,可以启动项目看看,访问项目下的任意一个路径都会被拦截要求输入用户名和密码,由于啥都还没有设置,所以用户名是user
,密码在启动springboot的时候在console输出了,类似f1ea0b2d-b053-40fd-abc5-065a4f45b266
这样的一串就是默认密码。
配置自己的用户名密码
在项目根目录下添加SecurityConfig.java
文件
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
然后访问任意一个URL都会跳转到下面这个界面
输入刚刚设置的用户名密码就可以重新跳转回所要访问的界面
这段代码内容很少,但事实上已经做了很多的默认安全验证,包括:
- 访问应用中的每个URL都需要进行验证
- 生成一个登陆表单
- 允许用户使用username和password来登陆
- 允许用户注销
- CSRF攻击拦截
- Session Fixation攻击
- 安全Header集成
配置自定义的登录界面
在SecurityConfig.java
中添加下面的代码,主要是说拦截所有请求,然后配置自定义的登陆界面的路径为“/login”
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage( "/login")
.permitAll();
}
在templates文件夹下添加login.html文件,我这个html文件是从之前写的直接拷贝过来的。
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html;charset=utf-8" />
<title>登录界面</title>
<style type="text/css">
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="starter-template">
<p th:if="${param.logout}" class="bg-warning">已成功注销</p>
<p th:if="${param.error}" class="bg-danger">有错误,请重试</p>
<h2>使用账号密码登陆</h2>
<form name="form" th:action="@{/login}" method="POST">
<div class="form-group">
<label for="username">账号:</label>
<input type="text" class="form-control" name="username" value="" placeholder="账号" />
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" class="form-control" name="password" placeholder="密码" />
</div>
<input type="submit" id="login" class="btn btn-primary" />
</form>
</div>
</div>
</body>
</html>
然后在添加一个文件WebMvcConfig
,将“/login”映射到“login.html”
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
这个时候重新访问刚刚的路径,就会拦截到自定义的登录界面
这里Html文件中的用户名密码,默认的name属性名字是“username”和“password”,否则springsecurity是获取不到值的,如需要自定的话,可以这么设置
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage( "/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll();
}
退出登录
可以在configure函数下继续设置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage( "/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index")
.invalidateHttpSession(true)
.permitAll();
}
可以直接从代码中看出来,就是访问“/logout”的时候会退出登录,若成功退出便会访问到“/index”这个路径,这个比较简单,就不贴代码出来了,可以下载我的源码去看看。
拦截url和授权
之前设置的都是拦截全部的URL,不管什么URL都拦截到登录界面,也可以自定义拦截,同样是在configure函数下添加设置:
- resources路径下的文件和以show开头的路径无需登录结课访问
- test该URl只能由用户为ADMIN的访问
- index该路径可由ADMIN和USER_ADMIN的用户访问
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers( "/resources/**", "/show*").permitAll()
.antMatchers( "/test").hasRole("ADMIN" )
.antMatchers( "/idnex").access("hasRole('ADMIN') and hasRole('USER_ADMIN')")
.antMatchers( "/AddUser").access("hasRole('ADMIN') and hasRole('USER')")
// .regexMatchers("").permitAll()正则表达式匹配
.anyRequest().authenticated()
// .authorizeRequests()
// .anyRequest().authenticated()
.and()
.formLogin()
.loginPage( "/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index")
.invalidateHttpSession(true)
.permitAll();
}
关于用户的权限,可在设置用户名和密码是指定
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("1").password("1").roles("ADMIN").and()
.withUser("2").password("2").roles("USER").and()
.withUser("3").password("3").roles("USER_ADMIN").and()
.withUser("4").password("4").roles("USER");
}
配置mysql数据库中的用户名密码
我数据库中一张表SysUser(id,username,password,role)
在java中新建一个entity类SysUser.java
注意这个类需要实现UserDetails接口。数据库中的role字段需要有”ROLE_”作为前缀,或者需要在构建的时候拼接“ROLE_”,我这里是拼接的。
@Entity
public class SysUser implements UserDetails{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private String role;
//getter....setter....
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
auths.add(new SimpleGrantedAuthority("ROLE_"+role));
return auths;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
新建一个JPA的类SysUserRepository.java
这个很简单就不做解释了。
import org.springframework.data.jpa.repository.JpaRepository;
public interface SysUserRepository extends JpaRepository<SysUser, Long>{
SysUser findByUsername(String username);
}
新建一个CustomUserService.java
文件
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;
public class CustomUserService implements UserDetailsService {
@Autowired
SysUserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) {
SysUser user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return user;
}
}
最后在SecurityConfig.java
文件中添加
@Bean
UserDetailsService customUserService(){
return new CustomUserService();
}
在configure方法下添加
auth.userDetailsService(customUserService());
这样就可以将数据库中的用户名密码添加到springsecurity中。
关于原理可以去参考一下http://www.tianshouzhi.com/api/tutorials/spring_security_4/250
测试这里便不做了,可以下载我的源码去试试看