SpringBoot+Shiro

整合思路

avatar

引入Shiro依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.0</version>
</dependency>


<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>

创建SpringBoot项目就不多说了

还需要引入一下jstl的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
         <!--        引入jsp解析依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- jstl-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>

application.properties文件配置为

1
2
3
4
5
6
server.port=8080
server.servlet.context-path=/shiro
spring.application.name=shiro

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

这样环境就配置好了

环境搭建

在webapp下创建index.jps和login.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@page contentType="text/html;  UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>系统主页V1.0</h1>

<ul>
<li><a href="">用户管理</a></li>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>

</body>
</html>

login.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@page contentType="text/html;  UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登陆页面</h1>
</body>
</html>

接着创建一个config包来存放Shiroconfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.ceit.springboot_jsp_shiro.config;

import com.ceit.springboot_jsp_shiro.shiro.realms.CustomerRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
* 用来整合Shiro框架的配置类
*/
@Configuration
public class ShiroConfig {
//1. 创建shiroFilter
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<String,String>();
map.put("/index.jsp","authc"); //authc请求这个资源需要认证和授权

//默认认证界面路径
shiroFilterFactoryBean.setLoginUrl("/login.jsp");

shiroFilterFactoryBean.setFilterChainDefinitionMap(map);


return shiroFilterFactoryBean;
}

//2. 创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

//给安全管理器设置realm
defaultWebSecurityManager.setRealm(realm);

return defaultWebSecurityManager;
}

//3. 创建自定义realm
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
return customerRealm;
}
}

同时还需要一个shiro.realms包来自定义realm
CustomerRealm.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.ceit.springboot_jsp_shiro.shiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

//自定义Realm
public class CustomerRealm extends AuthorizingRealm {

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}

最后运行访问一下index.jsp 要是成功跳转到了login.jsp 那就证明index.jsp现在是受保护的了

实现认证与退出

这个例子并没有连接数据库,使用一个值来进行演示

由于config肯定不能进行直接访问,所以我们需要一个controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.ceit.springboot_jsp_shiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("user")
public class UserController {

/**
* 退出登录
*/
@RequestMapping("logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout(); //退出登录
return "redirect:/login.jsp";
}

/**
* 用来处理身份验证
* @param username
* @param password
* @return
*/
@RequestMapping("login")
public String login(String username,String password){
//获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username,password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
return "redirect:/login.jsp";
}
}

同时也要去修改一下Realm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.ceit.springboot_jsp_shiro.shiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

//自定义Realm
public class CustomerRealm extends AuthorizingRealm {

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=====================================");
String principal = (String)authenticationToken.getPrincipal();
if("xiaochen".equals(principal)){
return new SimpleAuthenticationInfo(principal,"123",this.getName());
}
return null;
}
}

Config也需要进行修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.ceit.springboot_jsp_shiro.config;

import com.ceit.springboot_jsp_shiro.shiro.realms.CustomerRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
* 用来整合Shiro框架的配置类
*/
@Configuration
public class ShiroConfig {
//1. 创建shiroFilter
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<String,String>();
map.put("/user/login","anon"); //anon 设置为公共资源
map.put("/**","authc"); //authc 请求这个资源需要认证和授权

//默认认证界面路径
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);


return shiroFilterFactoryBean;
}

//2. 创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

//给安全管理器设置realm
defaultWebSecurityManager.setRealm(realm);

return defaultWebSecurityManager;
}

//3. 创建自定义realm
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
return customerRealm;
}
}

上面都有了之后,就可以修改一下index.jsp和login.jsp了

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@page contentType="text/html;  UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>系统主页V1.0</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出用户</a>
<ul>
<li><a href="">用户管理</a></li>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>

</body>
</html>

login.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@page contentType="text/html;  UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登陆页面</h1>

<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>

这样就完成了认证和退出

思路很简单,就是写一个controller控制器,去获取主体subject,并且调用subject中的login和logout方法去实现认证和退出,并且完成一下重定向的操作

subject呢会自动的注入到realm的配置中,所以可以直接使用

而realm中,为了方便演示,没有连接数据库去进行操作,使用了一个值去模拟登录和退出

连接数据库只需要去替换一下就可以了

连接数据库+MD5+Salt注册功能

MD5+Salt不仅是用于用户认证,在用户注册时就应该去进行一下MD5+Salt的操作

首先肯定是要有个注册页面了register.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@page contentType="text/html;  UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>用户注册</h1>

<form action="${pageContext.request.contextPath}/user/register" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"> <br>
<input type="submit" value="立即注册">
</form>
</body>
</html>

还要将它和请求都设置为公共资源

1
2
map.put("/user/register","anon");
map.put("/register.jsp","anon");

接下来就是新建一个数据库
avatar

除此之外还要添加mysql和mybatis相关的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--        mabatis相关依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

<!-- mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>

<!-- druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>

配置文件同样也要加上

1
2
3
4
5
6
7
8
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro_spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

mybatis.type-aliases-package=com.ceit.springboot_jsp_shiro.entity
mybatis.mapper-locations=classpath:com/ceit/mapper/*.xml

在resources下建一个com.ceit.mapper包用于存放mapper配置文件

在com.ceit.springboot_jsp_shiro下建一个entity包 创建一个User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.ceit.springboot_jsp_shiro.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String id;
private String username;
private String password;
private String salt;

public void setSalt(String salt) {
this.salt = salt;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

接着对应开发对应的dao接口 UserDAO.interface
1
2
3
4
5
6
7
8
9
package com.ceit.springboot_jsp_shiro.dao;

import com.ceit.springboot_jsp_shiro.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDAO {
void save(User user);
}

接下来就是配置UserDAOMapper.xml了
1
2
3
4
5
6
7
8
9
<?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.ceit.springboot_jsp_shiro.dao.UserDAO">

<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(#{id},#{username},#{password},#{salt})
</insert>

</mapper>

再创建一个service包 创建一个UserService.interface
1
2
3
4
5
6
7
8
package com.ceit.springboot_jsp_shiro.service;

import com.ceit.springboot_jsp_shiro.entity.User;

public interface UserService {
//注册用户
void register(User user);
}

对应的UserServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.ceit.springboot_jsp_shiro.service;

import com.ceit.springboot_jsp_shiro.dao.UserDAO;
import com.ceit.springboot_jsp_shiro.entity.User;
import com.ceit.springboot_jsp_shiro.utils.SaltUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDAO userDAO;

@Override
public void register(User user) {
//处理业务调用dao
//明文密码进行md5+salt+hash散列
//1. 生成随机盐
String salt = SaltUtils.getSalt(8);
//2. 将随机盐保存到数据
user.setSalt(salt);
//3. 明文密码进行md5+salt+hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
user.setPassword(md5Hash.toHex());

userDAO.save(user);
}
}

这里还需要创建一个工具类 先创建一个Utils包 包下创建一个SaltUtils.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.ceit.springboot_jsp_shiro.utils;

import java.util.Random;

public class SaltUtils {
/**
* 生成salt的静态方法
* @param n
* @return
*/
public static String getSalt(int n){
char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".toCharArray();
StringBuilder sb = new StringBuilder();
for(int i=0;i<n;i++) {
char aChar = chars[new Random().nextInt(chars.length)];
sb.append(aChar);
}
return sb.toString();
}

public static void main(String[] args) {
String salt = getSalt(4);
System.out.println(salt);
}
}

用于生成盐

接着在Controller中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Autowired
private UserService userService;

/**
* 用户认证
*/
@RequestMapping("register")
public String register(User user){
try {
userService.register(user);
return "redirect:/login.jsp";
} catch (Exception e) {
e.printStackTrace();
return "redirect:/register.jsp";
}
}

接下来进行一下测试,访问register.jsp 如果注册之后 跳转到了login.jsp 登陆一下 如果成功了 那么就完成了注册的一个流程,看一下数据库中也有了对应账号和加了盐的密码

注意事项:

  1. druid依赖 直接使用druid-spring-boot-starter

  2. xml需要加上<?xml version=”1.0” encoding=”UTF-8”?>

  3. dao包下的DAO别忘了加上@Mapper