一:使用背景.
1:生态环境.
构建Web系统安全的一整套解决方案,目前业界使用的最多的是Apache的Shiro和Spring的Security框架,Security框架功能更强大,权限控制粒度更细,使用的难度也较Shiro要难一些.对OAuth2支持更好,源于Spring,可以和Spring框架无缝整合.SpringBoot中提供了自动化配置方案,当然Shiro官方也提供了一个整合SpringBoot的Stater,也是比较方便的.
2:Spring Security简单介绍.
Spring Security是基于Spring框架的Web应用安全性解决方案.Web应用的安全性主要包括用户认证(Authentication)和用户授权(Authorization).Spring Security使用的是Servlet规范中标准的过滤器机制.
对于特定的请求,Spring Security 的过滤器会检查该请求是否通过认证,以及当前用户是否有足够的权限来访问此资源.
对于非法的请求,Spring Security过滤器会跳转至指定页面让用户进行认证,认证失败返回错误信息,重新认证.
用户认证指的是验证某个用户是否为系统的合法用户,也就是说用户是否能访问系统.用户认证一般要求用户提供用户名和用户密码.系统通过校验用户名和用户密码来完成用户认证.
用户授权指的是验证用户是否有权限执行某个操作.一个系统中,不同用户所具有的权限是不同的.系统会为不同的用户分配不同的角色,不同的角色则对应一系列权限.
二:环境搭建.
1:环境参数.
SpringBoot 2.1.0,Lombok 1.18.4,nekohtml 1.9.22,Thymeleaf 3.0.11.RELEASE.Spring-Security5.1.2
2:Maven依赖.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- Security的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf使用Security的标签-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!-- Spring的Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入MyBatis,注意一点的就是mybatis的版本号,这个不是spring的,是Mybatis的开发的.-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!-- MySQL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
<!-- 引入Thymeleaf模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--配置这个可以使鼠标点击就可以到那个配置类的 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- SpringBoot测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Security测试-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<optional>true</optional>
</dependency>
<!-- nekohtml-->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
SecurityConfig(Security的配置)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
项目中只需要添加Spring Security的依赖,项目中所有的资源都会被保护起来的.
三:实战演练.
1:编写一个史上最简易的Controller和视图页面.(Security默认的认证用户名和密码)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>【Index】</title>
</head>
<body>
<div align="center">
<h2>首页</h2>
</div>
</body>
</html>
IndexController.java
package com.example.security.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* author:
* date:
* time:
* description:
*/
@Controller
public class IndexController {
@RequestMapping("/index")
public String index(){
return "index";
}
}
2. 浏览器测试访问页面.
按下回车:
发现Web系统被保护起来了,访问该系统的任何资源,首先要认证一下的.
这里的默认的用户名是:user.密码是每次启动项目动态生成的,在控制台输出.
然后按照用户名和密码认证一下吧.认证成功后就跳转到刚才访问的页面了.
Security自定义用户名和密码.直接在application中配置用户名和密码.
下面的代码,分别配置了用户名和密码以及角色.
spring.security.user.name=admin
spring.security.user.password=123456
spring.security.user.roles=admin
然后重启启动项目.挨次访问
由于自己配置了用户名和密码,控制台不在输出密码了.
Security的基于内存的认证.
首先是Security重写configureGlobal(AuthenticationManagerBuilder auth)方法,(基于内存配置用户的角色的时候是不需要ROLE_前缀的)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
.withUser("User1").password(passwordEncoder().encode("123456")).roles("ADMIN","USER","Manager")
.and()
.withUser("User2").password(passwordEncoder().encode("123456")).roles("User");
String password=passwordEncoder().encode("123456");
System.out.println("{password:}"+password);
}
上述代码,需要将内存创建的用户名的密码进行加密了,之前的版本如Spring Boot 2.0.4是可以不需要加密的,
使用的PasswordEncoder()的NoOpPasswordEncoder()的,不过在SpringBoot 2.1.0中弃用了,使用BCryptPasswordEncoder()进行加密
当然了Security提供了多种加密的方法,但是一定要选一种进行加密的,否则认证不过的.
这里要说一下:如果不加密报错的分析如下:
Security5中新增了密码加密方式,并把原来的密码存储格式改为了{id}encodedPassword于是大概就明白了程序出错的原因: 前端传过来密码后,程序会查找被 花括号"{}"包括起来的id ,以此来确定后面的密码怎么进行加密,而我们在前面并没有按该格式进行处理,这就导致找不到id,就报错了.程序中可以打印输出一下加密后的结果.
下面启动项目.可以看到密码加密的格式.
下面进行测试访问.
现在还使用基于自定义配置的用户名和密码的认证是不通过的.
这里分别使用内存中穿建的User1和User2账户进行登录就是可以成功的.所以配置文件中的配置的用户名和密码以及角色就不用了,删除吧.
但是目前系统是可以实现认证功能的,但是受保护的资源都是默认的,而且不能根据项目需要进行角色管理,要实现个性化管理,就要重写HttpSecurity().
代码解读一下:
前面的antMatches("/admin/**)等三个是不需要认证即可访问,但是要有相应的权限的.至于后面的formLogin()的调用了登录接口
/login.permitAll()表示调用登录接口是不需要认证即可访问的.其他的URL都要认证后才可以访问的.截止目前登录表单页面都是使用的Spring Security提供的.配置了三个用户分别是User1,User2,User3,分别有不同的角色.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
.withUser("User1").password(passwordEncoder().encode("123456")).roles("ADMIN","USER","MANAGER")
.and()
.withUser("User2").password(passwordEncoder().encode("123456")).roles("ADMIN","USER")
.and()
.withUser("User3").password(passwordEncoder().encode("123456")).roles("USER");
String password=passwordEncoder().encode("123456");
System.out.println("{password:}"+password);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.access("hasAnyRole('ADMIN')")
.antMatchers("/manager/**")
.access("hasRole('ADMIN') and hasRole('Manager')")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf()
.disable();
}
测试样例:
使用User3账号登录.同样User1和User2都可以登录后访问该接口的.
http://localhost:8080/admin/hello
使用账号User3访问这个接口.403禁止访问.
使用账号User1和User2可以访问的
http://localhost:8080/manager/hello
只有User1登录后才可以访问的.
3: 使用自定义的登录页面,上面我们一直使用的是Spring Security的默认的登录页面.
3.1 要解决的问题就是,我们要处理静态资源无需认证就可以访问的.我们可以重写下面的方法,就是说静态的Wen资源可以无需认证.
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**","/fonts/**","/js/**","/img/**", "/webjars/**", "**/favicon.ico");
}
当然了可以将静态的Web资源统一放在一个目录里面,减少代码冗长.
3.2 定制我们自己的登录页面.
3.3 使用PageController来做一下页面路由.
PageController
@Controller
public class PageController {
@RequestMapping("/login")
public String login(){
return "login";
}
}
3.4 编写login.html.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>【登陆页面】</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet"
th:href="@{/css/bootstrap.min.css}">
<!-- Font Awesome -->
<link rel="stylesheet"
th:href="@{/css/font-awesome.min.css}">
<!-- Ionicons -->
<link rel="stylesheet" th:href="@{/css/ionicons.min.css}">
<!-- Theme style -->
<link rel="stylesheet" th:href="@{/css/AdminLTE.min.css}">
<!-- iCheck -->
<link rel="stylesheet" th:href="@{/css/iCheck/square/blue.css}">
<style type="text/css">
body{
overflow-y: auto;
overflow: hidden;
background: url(/img/back.jpg) no-repeat 0 0px;
background-attachment:fixed;
background-size:100%;
height:auto;
}
</style>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- Google Font -->
<!--<link rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">-->
</head>
<body class="hold-transition login-page">
<div class="login-box">
<div class="login-logo">
<h3><strong>SpringBoot Security学习项目</strong></h3>
</div>
<div class="login-box-body">
<p class="login-box-msg">用户登录</p>
<p th:if="${param.error}" class="bg-red">用户名或密码错误!</p>
<p th:if="${param.logout}" class="bg-yellow">您已注销成功!</p>
<form method="post" data-tgogle="validator" th:action="@{/login}">
<div class="form-group has-feedback">
<input type="text" name="username" class="form-control" placeholder="用户名" data-error="用户名不能为空!" required />
<span class="glyphicon glyphicon-user form-control-feedback"></span>
<div class="help-block with-errors"></div>
</div>
<div class="form-group has-feedback">
<input type="password" name="password" class="form-control" placeholder="密码" data-error="密码不能为空!" required />
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
<div class="help-block with-errors"></div>
</div>
<div style="color:red;" th:text="${error}"></div>
<div class="row">
<div class="col-xs-8">
<div class="checkbox icheck">
<label>
<input name="rememberMe" type="checkbox"> 记住密码
</label>
</div>
</div>
<!-- /.col -->
<div class="col-xs-4">
<button type="submit" class="btn btn-info btn-block">登陆</button>
</div>
<!-- /.col -->
</div>
</form>
<div class="social-auth-links text-center">
<div class="row">
<div class="col-xs-6">
<a th:href="@{/oauth/authlogin}" class="btn btn-block btn-social btn-success btn-flat"><i class="fa fa-github" aria-hidden="true"></i> Github登陆</a>
</div>
<div class="col-xs-6">
<a href="/OAuth/authLogin" class="btn btn-block btn-social btn-success btn-flat"><i class="fa fa-wechat"></i> 微信登陆</a>
</div>
<div class="col-xs-6">
<a href="#" class="btn btn-block btn-social btn-success btn-flat"><i class="fa fa-qq"></i> QQ登陆</a>
</div>
</div>
</div>
<!-- /.social-auth-links -->
<a href="#">忘记密码</a> |
<a href="#" class="text-center">注册</a>
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
<!-- jQuery 3 -->
<script th:src="@{/js/jquery.min.js}"></script>
<!-- Bootstrap 3.3.7 -->
<script th:src="@{/js/bootstrap.min.js}"></script>
<!-- iCheck -->
<script th:src="@{/js/icheck.min.js}"></script>
<!-- Bootstrap-validator-0.11.9 -->
<script th:src="@{/js/validator.min.js}"></script>
<script>
$(function () {
$('input').iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue',
increaseArea: '20%' /* optional */
});
});
</script>
</body>
</html>
登录默认名是username,用户密码是password.可以在configure中使用HttpSecurity来自定义的.usernameParameter() passwordParameter().
启动浏览器访问一下吧:
http://localhost:8080/admin/hello
使用账户User1进行登录后.
4: 下面再次添加一下home页面.以及获取登录用户信息.
PageController
@RequestMapping({"/","/home"})
public String home(){
return "home";
}
home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"/>
<link rel="stylesheet" th:href="@{/css/signin.css}"/>
<title>[Home Page]</title>
</head>
<body>
<div class="container">
<div align="center">
<h3>Spring Security学习主页</h3>
<div sec:authorize="!isAuthenticated()">
<h4>欢迎您访问Security,如要深入学习,请先<a th:href="@{/login}">登录</a></h4>
</div>
<div sec:authorize="isAuthenticated()">
<p class="bg-success">登录成功!</p>
<p><strong>登录用户为:</strong><span sec:authentication="name"></span></p>
<p><strong>登录用户角色为:</strong><span sec:authentication="principal.authorities"></span></p>
</div>
<p th:text="${user}">获取第三方用户信息:</p>
<div>
<form th:action="@{/logout}" method="post">
<input type="submit" th:value="注销登录">
</form>
</div>
</div>
</div>
</body>
</html>
SecurityConfig中添加登录成功后要跳转的路径.就是defaultSuccessUrl().
浏览器访问:
跳转至登录页面.
home页面可以获取到用户的登录信息和相对应的角色.以及注销.
注销一下.(真实注销要Session失效和删除一些对象的).
注意使用的Security5注意引入5的标签文件.
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
获取用户信息可以有前台获取的方式也有后台获取的方式:
前台获取:
sec:authentication="name"(获取用户信息).
sec:authentication="principal.authorities"(获取用户角色信息).
后台获取:
Authentication authentication=SecurityContextHolder.getContext().getAuthentication();
System.out.println("获取到的用户权限:"+authentication.getPrincipal().toString());
针对注销功能,默认使用的是/logout的接口,但是url后面跟?login很是不好,这里改为自定义的.
至此完成了SpringSecurity的入门整合使用,主要有默认用户,自定义用户,以及内存中配置用户和角色的使用和登录获取用户名以及角色的使用.包括自定义注销和登录,都是基于url的安全认证,后面介绍使用注解+数据库更高级的配置使用.