SpringSecurity是专门针对基于Spring项目的安全框架,充分利用了依赖注入和AOP来实现安全管控。在很多大型企业级系统中权限是最核心的部分,一个系统的好与坏全都在于权限管控是否灵活,是否颗粒化。在早期的SpringSecurity版本中我们需要大量的xml来进行配置,而基于SpringBoot整合SpringSecurity框架相对而言简直是重生了,简单到不可思议的地步。
SpringSecurity框架有两个概念认证和授权,认证可以访问系统的用户,而授权则是用户可以访问的资源,下面我们来简单讲解下SpringBoot对SpringSecurity安全框架的支持。
在网上大多数springboot和SpringSecurity集成的项目都是使用JPA,这里我打算用mybatis。
在SpringBoot项目中使用SpringSecurity安全框架实现用户认证
1.创建springboot项目
主要引入Mybatis、Security、Web、MySQL和
thymeleaf,由于页面实现我是使用thymeleaf模板,还有lombok来简化实体类代码
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
thymeleaf-extras-springsecurity4是集成过程中thymeleaf页面展示控制中需要用到的
2.设计数据库表
数据库表有 用户表,角色表,用户角色关系表三张表:
注意:数据库表之间没有外键,很多JPA自动生成数据库表的时候都包含有外键,我不太推荐使用外键,数据表与数据表之间有关联(Relationship)是肯定的,但是不一定要用外键,为什么?外键本质是一种约束,该约束决定了你在增删改查的时候都会有额外开销。【实际上数据库在处理外键的时候估计也是创建一个中间表根据中间表来做关联操作,完成后再删除】,涉及到外键的逻辑全部在应用代码里实现。
插入数据:
insert into SYS_USER (id,username, password) values (1,'admin', 'admin');
insert into SYS_USER (id,username, password) values (2,'abel', 'abel');
insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN');
insert into SYS_ROLE(id,name) values(2,'ROLE_USER');
insert into SYS_ROLE_USER(id,Sys_User_id,Sys_Role_id) values(1,1,1);
insert into SYS_ROLE_USER(id,Sys_User_id,Sys_Role_id) values(2,2,2);
3.springboot的一些配置
application.yml配置文件,主要是配置mybatis,mysql数据库和thymeleaf模板
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/数据库名字
username: 数据库帐号
password: 数据库密码
thymeleaf:
cache: false
suffix: .html
prefix: classpath:/templates/
mybatis:
type-aliases-package: 实体类路径
mapper-locations: classpath*:mapper/*.xml
4.实体类的编写
用来lombok使实体类变得简单
角色实体类
@Data
public class SysRole {
private Integer id;
private String name;
}
用户实体类,用户和角色属于一对多,所以在用户的实体中有多个角色对象。
@Data
public class SysUser implements UserDetails {
private Integer id;
private String username;
private String password;
private List<SysRole> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auths = new ArrayList<>();
List<SysRole> roles = getRoles();
for(SysRole role : roles)
{
auths.add(new SimpleGrantedAuthority(role.getName()));
}
return auths;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
实现了UserDetails
接口,UserDetails
是SpringSecurity
验证框架内部提供的用户验证接口,主要是来完成自定义用户认证功能,需要实现getAuthorities
方法内容,将定义的角色列表添加到授权的列表内。
5.mapper接口和xml文件
UserMapper接口很简单,只需要提供一个根据用户名查询的接口就可以了
public interface UserMapper {
SysUser findByUserName(@Param("username") String username);
}
注意:接口需要加上@Mapper
,会自动扫描注入,但我这里为什么不加呢,因为我在启动类中加入了@MapperScan("mapper接口包的路径")
这样所有在mapper包下的接口都会自动加上@Mapper
对应的UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.us.example.mapper.UserMapper">
<resultMap id="userMap" type="com.us.example.domain.SysUser">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<collection property="roles" ofType="com.us.example.domain.SysRole">
<result column="name" property="name"/>
</collection>
</resultMap>
<select id="findByUserName" resultMap="userMap">
select u.*
,r.name
from Sys_User u
LEFT JOIN sys_role_user sru on u.id= sru.Sys_User_id
LEFT JOIN Sys_Role r on sru.Sys_Role_id=r.id
where username= #{username}
</select>
</mapper>
这里使用到了关联查询,在上面说了,涉及到外键的逻辑全部在应用代码里实现。
6.自定义SpringSecurity用户认证
下面我们实现SpringSecurity
内的UserDetailsService
接口来完成自定义查询用户的逻辑
其实就是在用户service
中实现UserDetailsService
接口,重写loadUserByUsername
方法获得userdetails
类型用户
@Service
public class CustomUserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
// 重写loadUserByUsername 方法获得 userdetails 类型用户
@Override
public UserDetails loadUserByUsername(String username) {
SysUser user = userMapper.findByUserName(username);
if (user != null) {
return user;
} else {
throw new UsernameNotFoundException("admin: " + username + " do not exist!");
}
}
}
因为角色的添加已经在用户实体类中加入了,所以查询到用户不为空,直接返回用户对象就可以了。
7.配置SpringSecurity
自定义用户认证已经编写完成,下面我们需要配置SpringBoot项目支持SpringSecurity安全框架
继承WebSecurityConfigurerAdapter
,并重写它的方法来设置一些web安全的细节
configure(HttpSecurity http)方法
通过authorizeRequests()定义哪些URL需要被保护、哪些不需要被保护。
我们这里配置了登录页面/login
请求地址以及登录错误页面/login?error
不被SpringSecurity
拦截。
通过formLogin()定义当需要用户登录时候,转到的登录页面。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService() {
return new CustomUserService();
}
/**
* 配置忽略的静态文件,不加的话,登录之前页面的css,js不能正常使用,得登录之后才能正常.
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略URL
web.ignoring().antMatchers("/**/*.js", "/lang/*.json", "/**/*.css", "/**/*.js", "/**/*.map", "/**/*.html",
"/**/*.png");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() //任何请求,登录后可以访问
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")//登录失败 返回error
.permitAll() //登录页面用户任意访问
.and()
.logout().permitAll(); //注销行为任意访问
}
}
这里主要是注入用户自定义认证的实体,配置忽略的静态文件和配置拦截
8.页面编写
主要有两个页面
在src/resource 目录下新建static/css 目录,并放入js 文件 bootstrap.min.css
在src/resource目录下 templates 文件夹,里面存放静态页面
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>
我们简单的创建一个login.html
页面,页面里面添加一个简单的表单提交,我们的表单提交地址这里要注意了,SpringSecurity
内部已经给我们定义好了,在4.0版本之后登录地址都是/login
,当然这个/login
并不是我们上面配置的loginPage地址。这个地址如果直接访问是访问不到的。必须采用Post
形式访问
home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title sec:authentication="name"></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>
<li><a th:href="@{/admin}"> admin </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')"> <!-- 用户类型为ROLE_ADMIN 显示 -->
<p class="bg-info" th:text="${msg.etraInfo}"></p>
</div>
<div sec:authorize="hasRole('ROLE_USER')"> <!-- 用户类型为 ROLE_USER 显示 -->
<p class="bg-info">无更多信息显示</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" class="btn btn-primary" value="注销"/>
</form>
</div>
</div>
</body>
</html>
我们要根据角色,在用户登录成功后显示不同的内容,在这之前我们需要添加SpringSecurity
为我们提供的JSTL
标签库(也就是引入thymeleaf-extras-springsecurity4
依赖),就可以根据标签库自行判断登录用户的角色。
sec:authorize
就是引入后才能用的
9.编写controller实现页面跳转
controller
其实就相当于SpringBoot
内的MVC
控制器跳转,控制器配置类继承WebMvcConfigurerAdapter
类,重写addViewControllers()
方法添加路径访问。
一般我们都是写controller就好
这里我打算一个返回信息实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Msg {
private String title;
private String content;
private String etraInfo;
}
然后我们编写控制器 HomeController
@Controller
public class HomeController {
@RequestMapping("/")
public String index(Model model){
Msg msg = new Msg("测试标题","测试内容","额外信息,只对管理员显示");
model.addAttribute("msg", msg);
return "home";
}
@RequestMapping("/admin")
@ResponseBody
public String hello(){
return "hello admin";
}
@RequestMapping("/login")
public String login(){
return "login";
}
}
由于我们在application.yml中配置了页面的前缀后缀,所以页面跳转很简单。
10.测试验证
SpringSecurity会直接给我们重定向到我们配置的登录页面login.html
管理员登录,用户名admin,密码admin
普通成员登录,用户名abel,密码abel
完成了角色判断,后面将会加上权限。
源码地址github地址:https://github.com/JinBinPeng/SpringBoot-mybatis-springsecurity
参考文章:
http://www.jianshu.com/p/c3b49d0a490b
https://www.cnblogs.com/dailiang1993/p/7803550.html