Spring Security
Spring Security是专门针对基于Spring的项目的安全框架,充分利用了依赖注入和AOP来实现安全的功能,在早期的Spring Security的版本,使用Spring Security需要使用大量的XML配置,而SpringBoot中将全部基于Java配置和注解来实现Spring Security的功能
安全框架有两个重要概念,即认证(Authentication)和授权(Authorization),认证即确定用户可以访问当前系统,授权即确定用户在当前系统下所拥有的功能权限
用户认证
认证需要我们有一套用户数据的来源,而授权则是对于某个用户有相应的角色权限,在Spring Security里我们通过重写
protected void configure(AuthenticationManagerBuilder auth)
方法来实现定制
内存中的用户
使用AuthenticationManagerBuilder 的InMemoryAuthentication方法即可添加在内存中的用户,并可给用户指定角色权限
@Override
protected void configure (AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().WithUser("zhaozhen").password("zhaozhen").roles("ROLE_ADMIN")
.add()
.withUser("zhao")..password("1234").roles("ROLE_USER")
}
JDBC中的用户
JDBC中的用户直接指定dataSource即可
@Autowired
DataSource dataSource
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.jdbcAuthentication().dataSourc(dataSource);
}
通用的用户
上面的两种用户和权限的获取方式只限于内存或者JDBC,我们的数据访问方式是多种多样的,可以是非关系型数据库,也可以是我们常用的JPA等
在此我们不再赘述,此种方式将在接下来的实践中介绍
SpringBoot的支持
SpringBoot为SpringSecurity提供了许多的自动配置
比如:
- 自动配置了一个内存中的user,密码在 程序运动时出现。
- 忽略对js和css等静态文件的拦截。
- 自动配置的securityFilterChainRegistration的Bean
我们也可以在properties配置以”security”为前缀的与Spring Security相关的配置
当我们需要自己扩展的配置,只需配置类继承WebSecurityConfigurerAdapter类即可,无需使用@EnableWebSecurity注解
实战
1.配置
application.properties
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
logging.level.org.springframework.security = INFO
spring.thymeleaf.cache=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
pom.xml文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
用户和角色
用户
@Entity
public class SysUser implements UserDetails {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
@ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)
private List<SysRole> roles;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auths= new ArrayList<GrantedAuthority>();
List<SysRole> roles = this.getRoles();
for (SysRole role :roles){
auths.add(new SimpleGrantedAuthority(role.getName()));
}
return auths;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
}
上述实体实现UserDetails接口,我们的用户实体即为Spring Security所使用的用户。重写了getAuthorities方法,将用户的角色作为权限
角色
@Entity
public class SysRole {
@Id
@GeneratedValue
private Long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
传值对象
数据展示对象
public class Msg {
private String title;
private String content;
private String 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;
}
public Msg(String title, String content, String etraInfo) {
super();
this.title = title;
this.content = content;
this.etraInfo = etraInfo;
}
}
数据访问
public interface SysUserRepository extends JpaRepository<SysUser,Long>{
SysUser findByUsername(String username);
}
自定义UserDetailsService
public class CustomerUserService implements UserDetailsService{
@Autowired
SysUserRepository sysUserRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = sysUserRepository.findByUsername(username);
if (user == null){
throw new UsernameNotFoundException("用户名不存在");
}
return user;
}
}
我们通过重写loadUserByUsername获得用户
配置
SpringMVC配置
@Configuration
public class WebMVCConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
Spring Security配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Bean
UserDetailsService customerUserService(){
return new CustomerUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customerUserService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest()
.authenticated().and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.permitAll()
.and()
.logout().permitAll();
}
}
我们定制登录和注销行为。
登录login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html;charset=UTF-8"/>
<title>登录页面</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
<style type="text/css">
body {
padding-top: 50px;
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security演示</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a th:href="@{/}"> 首页 </a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
<div class="starter-template">
<p th:if="${param.logout}" class="bg-warning">已成功注销</p><!-- 1 -->
<p th:if="${param.error}" class="bg-danger">有错误,请重试</p> <!-- 2 -->
<h2>使用账号密码登录</h2>
<form name="form" th:action="@{/login}" action="/login" method="POST"> <!-- 3 -->
<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" value="Login" class="btn btn-primary" />
</form>
</div>
</div>
</body>
</html>
首页
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"><!-- 1 -->
<head>
<meta content="text/html;charset=UTF-8"/>
<title sec:authentication="name"></title> <!-- 2 -->
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<style type="text/css">
body {
padding-top: 50px;
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security演示</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a th:href="@{/}"> 首页 </a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
<div class="starter-template">
<h1 th:text="${msg.title}"></h1>
<p class="bg-primary" th:text="${msg.content}"></p>
<div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 3 -->
<p class="bg-info" th:text="${msg.etraInfo}"></p>
</div>
<div sec:authorize="hasRole('ROLE_USER')"> <!-- 4-->
<p class="bg-info">无更多信息显示</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" class="btn btn-primary" value="注销"/><!-- 5 -->
</form>
</div>
</div>
</body>
</html>
登陆界面
管理员登录所看到的内容
普通用户登录看到的内容
参考书籍JavaEE的颠覆者–SpringBoot实战,代码