SpringBoot中使用SpringSecurity实现认证和受权(入门)

简介

SpringSecurity 是Spring项目组中用来提供安全认证(authentication)和受权(authorization)服务的框架。所谓的认证通俗的说就是判断正在操做的用户和密码是否匹配,而受权就是控制用户能作什么操做,也就是能干什么能看到什么。html

环境搭建

以idea开发工具为例,模板引擎使用的是thymeleaf
pom.xmljava

<properties>
		<java.version>1.8</java.version>
		<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
		<thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
复制代码

添加一些模板文件,这些拷贝便可
KungfuController.javamysql

@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.htmlweb

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>罗汉拳</h1>
	<p>罗汉拳站当央,打起来不要慌</p>
</body>
</html>
复制代码

level1/2.html算法

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>武当长拳</h1>
	<p>长一点在长一点</p>
</body>
</html>
复制代码

level1/3.htmlspring

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>全真剑法</h1>
	<p>全都是真的</p>
</body>
</html>
复制代码

level2/1.htmlsql

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>太极拳</h1>
	<p>
	       一个西瓜圆又圆 劈它一刀成两半 你一半来 给你你不要 给他他不收 那就不给 把两人撵走 他们不走你走 走啦,一挥手,伤自尊
                  不买西瓜别缠我,缓慢纠缠様 两人缠我赖皮,手慢动做左右挥动 看我厉害,转头缓步拍苍蝇状 拍死了,手抱西瓜状+奥特曼十字手+广播操准备运动的站立
    </p>
</body>
</html>
复制代码

level2/2.html数据库

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>七伤拳</h1>
	<p>练这拳的人全都死了</p>
</body>
</html>
复制代码

level2/3.html浏览器

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>梯云纵</h1>
	<p>踩本身的脚往上跳</p>
</body>
</html>
复制代码

level3/1.html安全

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>葵花宝典</h1>
	<p>欲练神功,挥刀自宫</p>
</body>
</html>
复制代码

level3/2.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>龟派气功</h1>
	<p>龟-派-气-功-波</p>
</body>
</html>
复制代码

level3/3.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>独孤九剑</h1>
	<p>欲练此剑,必先犯贱</p>
</body>
</html>
复制代码

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>登陆错误</h1>
</body>
</html>
复制代码

welcome.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</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.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </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"><!-- 方法须要时post -->
		<input type="submit" value="注销"/>
	</form>
复制代码

这时候咱们再次重启项目访问项目地址登陆以后就能够注销了,注销以后就不能访问任何须要角色的页面了。

接下来再增长认证受权在前台页面的应用
前台页面代码以下

<!DOCTYPE html>
<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 here</title>
</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"><!-- 方法须要时post -->
		<input type="submit" value="注销"/>
	</form>
</div>
<hr>

<div sec:authorize="hasRole('VIP1')"><!-- 对拥有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')"><!-- 对拥有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')"><!-- 对拥有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><!-- 在properties控制下版本 -->
    	<thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version>
   </properties
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    </dependency>
复制代码

这时候咱们再次访问登陆就能够看到页面上所登陆用户的不一样展现的差别了

记住登陆状态
此时咱们登陆以后关闭浏览器再次启动后,访问项目须要从新登陆。若是可以记住登陆状态,之后只须要登陆一次,只要不注销,重启浏览器访问也依然是登陆状态。
开启记住我功能
在配置类的configure(HttpSecurity http)方法中添加

http.rememberMe();
复制代码

当咱们添加上面的配置时,登陆页面就会自动加上一个记住的按钮。SpringSecurity是将登陆信息存储到cookie中,默认存在的时间为14天。可是若是点击注销,就会当即清除这个cookie信息。
指定本身的登陆页面,

  1. 添加login.html的登陆请求/userlogin,并绑定input输入框
    <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>
    复制代码
  2. 修改默认登陆请求
http.formLogin().usernameParameter("user").passwordParameter("pwd")//指定登陆input标签名称
            .loginPage("/userlogin").failureUrl("/loginError");//指定定制的登陆请求和登陆失败后的请求
            http.rememberMe().rememberMeParameter("remeber");//添加记住标签的参数名称
复制代码
  1. 将welcome.html页面的登陆请求更换为/userlogin
<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.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.4</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.10</version>
		</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

第一次写博客,水平有限,思路也有问题,语言组织的也有问题,但愿谅解

相关文章
相关标签/搜索