Shiro在SpringBoot中配置

一、什么是Shiro

​ Shiro是一个很易用与Java项目的的安全框架,主要提供了提供了认证、授权、加密、会话管理功能。

二、Shiro主要记住哪些

  • ShiroFilterFactory Shiro核心类
  • SecurityManager 用于管理所有的Subject
  • Subject 当前用户操作
  • Realms 用于进行权限信息的验证,也是我们需要自己实现的。

三、步骤:

1.pom.xml中添加Shiro依赖

1
2
3
4
5
<dependency>  
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>

2.注入ShiroFilterFactory和SecurityManager


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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* shiro配置
*
* @Description:
* @author: gaojindeng
* @Time: 2018年1月6日 下午4:31:00
*
*/
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断

// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/image/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/register", "anon");
filterChainDefinitionMap.put("/index/**", "anon"); // 首页
filterChainDefinitionMap.put("/charts/**", "anon");// 图表
filterChainDefinitionMap.put("/statistics/**", "anon");// 统计
filterChainDefinitionMap.put("/about/**", "anon");// 关于
// filterChainDefinitionMap.put("/setup/**", "authc");
// filterChainDefinitionMap.put("/capital/entrustment",
// "perms[user:admin:*]");
// filterChainDefinitionMap.put("/capital/entrustment", "authc");
// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}

@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
return securityManager;
}

/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}

/**
* 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}

/**
* 该方法不加上,权限认证也可以通过
*
* @return
*/
/* @Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor()
{
return new LifecycleBeanPostProcessor();
}*/

/**
* 不加上这个 权限注解通过不了
*
* @return
*/
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}

/**
* 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码; )
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);// 散列的次数,比如散列两次,相当于
// md5(md5(""));
return hashedCredentialsMatcher;
}
}



注入ShiroFactory==>SecurityManager==>Realm
Realm是实现权限认证、授权的,所有要自己手动新建这个类

### 3.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
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class MyShiroRealm extends AuthorizingRealm {

@Autowired
private IUserSV userSV;

/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

// 获取用户的输入的账号.
String username = (String) token.getPrincipal();

// 通过username从数据库中查找 User对象,如果找到,没找到.
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
User userInfo = userSV.queryByName(username);
if (userInfo == null) {
return null;
}
// 加密方式;
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名
userInfo.getPassword(), // 密码
ByteSource.Util.bytes(userInfo.getSalt()), // //salt=username+salt
getName() // realm name
);

// 明文: 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
// SimpleAuthenticationInfo authenticationInfo = new
// SimpleAuthenticationInfo(
// userInfo, //用户名
// userInfo.getPassword(), //密码
// getName() //realm name
// );
return authenticationInfo;
}

/**
* 此方法调用 hasRole,hasPermission的时候才会进行回调.
*
* 权限信息.(授权): 1、如果用户正常退出,缓存自动清空; 2、如果用户非正常退出,缓存自动清空;
* 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。 (需要手动编程进行实现;放在service进行调用)
* 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例, 调用clearCached方法;
* :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/*
* 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行, 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;
* 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了, 缓存过期之后会再次执行。
*/
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");

SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User userInfo = (User) principals.getPrimaryPrincipal();

// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
// UserInfo userInfo = userInfoService.findByUsername(username)

// 权限单个添加;
// authorizationInfo.addRole("admin");
// 添加权限
// authorizationInfo.addStringPermission("userInfo:query");

for (Role role : userInfo.getRoles()) {
authorizationInfo.addRole(role.getType());
for (Permission p : role.getPermissions()) {
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}

}


4.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
// 登录提交地址和配置文件的登录url一致 
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
System.out.println("HomeController.login()");
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
String exception = (String) request.getAttribute("shiroLoginFailure");

System.out.println("exception=" + exception);
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
System.out.println("UnknownAccountException -- > 账号不存在:");
msg = "UnknownAccountException -- > 账号不存在:";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
System.out.println("IncorrectCredentialsException -- > 密码不正确:");
msg = "IncorrectCredentialsException -- > 密码不正确:";
} else if ("kaptchaValidateFailed".equals(exception)) {
System.out.println("kaptchaValidateFailed -- > 验证码错误");
msg = "kaptchaValidateFailed -- > 验证码错误";
} else {
msg = "else >> " + exception;
System.out.println("else -- >" + exception);
}
}
map.put("msg", msg);
// 此方法不处理登录成功,由shiro进行处理.
return "/login";
}


5.没有权限页面跳转

​ 新建全局异常类

1
2
3
4
5
6
7
8
9
@ControllerAdvice
public class ExceptionHandle {

@ExceptionHandler(value = UnauthorizedException.class) // 处理访问方法时权限不足问题
@ResponseBody
public String handle(Exception e) {
return "<script>alert('没有权限访问!');window.history.back();</script>";
}
}

四、其他

1.获取密文:

1
Object obj = new SimpleHash("md5", user.getPassword(), ByteSource.Util.bytes(user.getSalt()) , 2);	//获取MD5加密后的密文 2是加密次数

2.错误页面处理

​ 新建error文件夹,404,500错误会自动找到该页面

​ springBoot错误,也可以新建error.html视图,也会自动找该页面

3.为什么要用盐

​ 希望即使两个原始密码相同,加密得到的两个字符串也不同。

4.前端怎么传值过来

​ 表单中的参数固定name写法是username、password、rememberMe。

lightquant wechat
欢迎您订阅灯塔量化公众号!