SpringSecurity 是Spring项目组中用来提供安全认证(authentication)和授权(authorization)服务的框架。所谓的认证通俗的说就是判断正在操作的用户和密码是否匹配,而授权就是控制用户能做什么操作,也就是能干什么能看到什么。
以idea开发工具为例,模板引擎使用的是thymeleaf
pom.xml
<properties>
<java.version>1.8java.version>
<thymeleaf.version>3.0.9.RELEASEthymeleaf.version>
<thymeleaf-layout-dialect.version>2.3.0thymeleaf-layout-dialect.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
添加一些模板文件,这些拷贝即可
KungfuController.java
@Controller
public class KungfuController {
private final String PREFIX = "pages/";
/**
* 欢迎页
* @return
*/
@GetMapping("/")
public String index() {
return "welcome";
}
/**
* 登陆页
* @return
*/
@GetMapping("/userlogin")
public String loginPage() {
return PREFIX+"login";
}
/**
* level1页面映射
* @param path
* @return
*/
@GetMapping("/level1/{path}")
public String level1(@PathVariable("path")String path) {
return PREFIX+"level1/"+path;
}
/**
* level2页面映射
* @param path
* @return
*/
@GetMapping("/level2/{path}")
public String level2(@PathVariable("path")String path) {
return PREFIX+"level2/"+path;
}
/**
* level3页面映射
* @param path
* @return
*/
@GetMapping("/level3/{path}")
public String level3(@PathVariable("path")String path) {
return PREFIX+"level3/"+path;
}
/**
* 登录失败页
* @return
*/
@GetMapping("/loginError")
public String loginError() {
return "loginError";
}
}
level1/1.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>罗汉拳h1>
<p>罗汉拳站当央,打起来不要慌p>
body>
html>
level1/2.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>武当长拳h1>
<p>长一点在长一点p>
body>
html>
level1/3.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>全真剑法h1>
<p>全都是真的p>
body>
html>
level2/1.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>太极拳h1>
<p>
一个西瓜圆又圆 劈它一刀成两半 你一半来 给你你不要 给他他不收 那就不给 把两人撵走 他们不走你走 走啦,一挥手,伤自尊
不买西瓜别缠我,缓慢纠缠様 两人缠我赖皮,手慢动作左右挥动 看我厉害,转头缓步拍苍蝇状 拍死了,手抱西瓜状+奥特曼十字手+广播操准备运动的站立
p>
body>
html>
level2/2.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>七伤拳h1>
<p>练这拳的人全都死了p>
body>
html>
level2/3.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>梯云纵h1>
<p>踩自己的脚往上跳p>
body>
html>
level3/1.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>葵花宝典h1>
<p>欲练神功,挥刀自宫p>
body>
html>
level3/2.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>龟派气功h1>
<p>龟-派-气-功-波p>
body>
html>
level3/3.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a th:href="@{/}">返回a>
<h1>独孤九剑h1>
<p>欲练此剑,必先犯贱p>
body>
html>
login.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title heretitle>
head>
<body>
<h1 align="center">欢迎登陆武林秘籍管理系统h1>
<hr>
<div align="center">
<form action="" method="post">
用户名:<input name=""/><br>
密码:<input name=""><br/>
<input type="submit" value="登陆">
form>
div>
body>
html>
loginError.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>登录错误h1>
body>
html>
welcome.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<h1 align="center">欢迎光临武林秘籍管理系统h1>
<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录a>h2>
<hr>
<h3>普通武功秘籍h3>
<ul>
<li><a th:href="@{/level1/1}">罗汉拳a>li>
<li><a th:href="@{/level1/2}">武当长拳a>li>
<li><a th:href="@{/level1/3}">全真剑法a>li>
ul>
<h3>高级武功秘籍h3>
<ul>
<li><a th:href="@{/level2/1}">太极拳a>li>
<li><a th:href="@{/level2/2}">七伤拳a>li>
<li><a th:href="@{/level2/3}">梯云纵a>li>
ul>
<h3>绝世武功秘籍h3>
<ul>
<li><a th:href="@{/level3/1}">葵花宝典a>li>
<li><a th:href="@{/level3/2}">龟派气功a>li>
<li><a th:href="@{/level3/3}">独孤九剑a>li>
ul>
body>
html>
项目的目录结构如下:
然后我们可以先启动项目,访问下项目地址,检查下环境是否正常:
首先引入SpringSecurity的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
增加一个配置类config/MySecurityConfig.java,需要继承WebSecurityConfigurerAdapter
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
//定制请求的授权规则
http.authorizeRequests().antMatchers("/").permitAll()//访问/路径不需要拥有任何角色
.antMatchers("/level1/**").hasRole("VIP1")//访问此路径需要VIP1角色
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
}
}
这时候我们再重启,访问每个需要角色的路径时就会被拒绝访问
这时候需要我们添加自动登录的功能(在上面的configure(HttpSecurityhttp)方法中添加),会给我们生成一个自动登录到页面,发送指定的请求/login会来到登录页面,如果登录失败会重定向到/login?error表示登录失败
http.formLogin();
如果没有登录访问需要权限的url就会来到这个登录页面。
具体的登录信息我们可以在类中定义,也可以在数据库中定义。
定义认证规则
首先重写configure(AuthenticationManagerBuilder auth)方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//这里定义的是从内存中取出用户名和密码,后面的是数据库中取出来验证
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("VIP1","VIP2")//分别表示登录名,登录密码,此用户拥有的角色
.and()
.withUser("lisi").password("123456").roles("VIP2","VIP3")
.and()
.withUser("wangwu").password("123456").roles("VIP1","VIP3");
}
定义完认证规则之后我们可以重启项目,再次访问,这时候访问需要角色的页面时会重定向到登录页面,这时候我们输入上面定义的任一用户登录信息,登录成功后即可访问对应角色的页面。
接下来我们可以加上注销功能
MySecurityConfig配置类configure(HttpSecurity http)方法中加入自动注销功能
//注销成功会默认返回/login?logout
http.logout().logoutSuccessUrl("/");//指定注销成功以后来到首页
//前台页面post请求访问/logout表示用户注销,并且清空session
前台页面welcome.html加上退出表单
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
form>
这时候我们再次重启项目访问项目地址登录之后就可以注销了,注销之后就不能访问任何需要角色的页面了。
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<h1 align="center">欢迎光临武林秘籍管理系统h1>
<div sec:authorize="!isAuthenticated()">
<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录a>h2>
div>
<div sec:authorize="isAuthenticated()">
<h2><span sec:authentication="name">span>,您好,您的角色有:
<span sec:authentication="principal.authorities">span>h2>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
form>
div>
<hr>
<div sec:authorize="hasRole('VIP1')">
<h3>普通武功秘籍h3>
<ul>
<li><a th:href="@{/level1/1}">罗汉拳a>li>
<li><a th:href="@{/level1/2}">武当长拳a>li>
<li><a th:href="@{/level1/3}">全真剑法a>li>
ul>
div>
<div sec:authorize="hasRole('VIP2')">
<h3>高级武功秘籍h3>
<ul>
<li><a th:href="@{/level2/1}">太极拳a>li>
<li><a th:href="@{/level2/2}">七伤拳a>li>
<li><a th:href="@{/level2/3}">梯云纵a>li>
ul>
div>
<div sec:authorize="hasRole('VIP3')">
<h3>绝世武功秘籍h3>
<ul>
<li><a th:href="@{/level3/1}">葵花宝典a>li>
<li><a th:href="@{/level3/2}">龟派气功a>li>
<li><a th:href="@{/level3/3}">独孤九剑a>li>
ul>
div>
body>
html>
我们还需要在pom文件中加一个依赖:SpringSecurity和thymeleaf的整合模块
<properties>
<thymeleaf-extras-springsecurity4.version>3.0.2.RELEASEthymeleaf-extras-springsecurity4.version>
properties
>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
dependency>
这时候我们再次访问登录就可以看到页面上所登录用户的不同展示的差异了
记住登录状态
此时我们登录之后关闭浏览器再次启动后,访问项目需要重新登录。如果能够记住登录状态,以后只需要登录一次,只要不注销,重启浏览器访问也依然是登录状态。
开启记住我功能
在配置类的configure(HttpSecurity http)方法中添加
http.rememberMe();
当我们添加上面的配置时,登录页面就会自动加上一个记住的按钮。SpringSecurity是将登录信息存储到cookie中,默认存在的时间为14天。但是如果点击注销,就会立即清除这个cookie信息。
指定自己的登录页面,
<form th:action="@{/userlogin}" method="post">
用户名:<input name="user"/><br>
密码:<input name="pwd"><br/>
<input type="checkbox" name="remeber">记住我 <br/>
<input type="submit" value="登陆">
form>
http.formLogin().usernameParameter("user").passwordParameter("pwd")//指定登录input标签名称
.loginPage("/userlogin").failureUrl("/loginError");//指定定制的登录请求和登录失败后的请求
http.rememberMe().rememberMeParameter("remeber");//添加记住标签的参数名称
<a th:href="@{/userlogin}">请登录a>
创建几张表如下:
CREATE TABLE USER(
user_id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(30),
PASSWORD VARCHAR(30)
)
CREATE TABLE role(
role_id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(30)
)
CREATE TABLE user_role(
role_id INT,
user_id INT
)
-- 插入数据 这里使用的是MD5加密算法,密码是123456
INSERT INTO role(NAME)VALUES('ROLE_VIP1');
INSERT INTO role(NAME)VALUES('ROLE_VIP2');
INSERT INTO role(NAME)VALUES('ROLE_VIP3');
INSERT INTO USER(NAME,PASSWORD)VALUES('zhangsan','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO USER(NAME,PASSWORD)VALUES('list','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO USER(NAME,PASSWORD)VALUES('wangwu','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO user_role(role_id,user_id)VALUES(1,1);
INSERT INTO user_role(role_id,user_id)VALUES(2,1);
INSERT INTO user_role(role_id,user_id)VALUES(3,2);
INSERT INTO user_role(role_id,user_id)VALUES(2,2);
INSERT INTO user_role(role_id,user_id)VALUES(3,3);
引入pom文件
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.4version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
加入application.properties配置信息
server.port=8085
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
编写实体类UserInfo
public class UserInfo implements Serializable {
private Integer userId;
private String name;
private String password;
private List<Role> roles;
//...
}
编写实体类Role
public class Role implements Serializable {
private Integer roleId;
private String name;
//...
}
编写dao
UserMapper
public interface UserMapper {
@Select("select * from user where name=#{username}")
@Results({
@Result(id = true, property = "userId", column = "user_id"),
@Result(property = "name", column = "NAME"),
@Result(property = "password", column = "PASSWORD"),
@Result(property = "roles",column = "user_id",javaType = java.util.List.class,many = @Many(select = "com.example.mapper.RoleMapper.findRoleByUserId"))
})
UserInfo findByUsername(String username);
}
RoleMapper
public interface RoleMapper {
@Select("select * from role where role_id in (select role_id from user_role where user_id=#{userId})")
@Results({
@Result(id = true, property = "roleId", column = "role_id"),
@Result(property = "name", column = "name")
})
Role findRoleByUserId(Integer userId);
}
编写service
UserService需要继承UserDetailsService
public interface UserService extends UserDetailsService {//继承UserDetailsService
UserInfo findByUsername(String username);
}
实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public UserInfo findByUsername(String username) {
return userMapper.findByUsername(username);
}
/**
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
try {
userInfo = userMapper.findByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(userInfo);
User user = new User(userInfo.getName(),userInfo.getPassword(),getAuthority(userInfo.getRoles()));
return user;
}
public List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
List<SimpleGrantedAuthority> list = new ArrayList<>();
for (Role role : roles) {
list.add(new SimpleGrantedAuthority(role.getName()));
}
return list;
}
}
配置类MySecurityConfig中需要修改configure(AuthenticationManagerBuilder auth)方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService())//user Details Service验证
.passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {//指定下加密规则
return MD5Utils.encode((String) rawPassword);
}
@Override//匹配接收到的密码是否和数据库中查询到的一致
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5Utils.encode((String)rawPassword));
}
});
}
最后加密类
public class MD5Utils {
private static final String SALT = "tamboo";
public static String encode(String password) {
password = password + SALT;
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
throw new RuntimeException(e);
}
char[] charArray = password.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
}
现在可以访问项目地址,登录的密码是123456
第一次写博客,水平有限,思路也有问题,语言组织的也有问题,希望谅解