在之前的文章中,分别介绍了springboot启动,在springboot中使用redis作为缓存,在springboot中使用jpa和mybatis,现在将开始一个更大的综合工程:一步步介绍在springboot中使用shiro+redis-cluster+cookie实现一个跨域单点登录的方案。
现在开始是第一篇:在springboot项目中接入shiro
引入shiro的maven依赖
1 | <dependency> |
程序具体实现
首先,需要一个shiro的配置文件,这里使用的是ymal配置。配置文件:application.yml
1 | shiro: |
说明:
realm: com.xxx.xxx.config.security.MyRealm
Realm,在shiro中相当于数据源,shiro的其他核心组件需要获取用户和认证数据,就是从Realm获取。
为了获得更好的自定义功能,通常我们会自己实现一个Realm.
所以这里使用realm: com.xxx.xxx.config.security.MyRealm配置了一个自定义的Realm。
loginUrl,定义了需要认证用户时,跳转到的登录页面
successUrl,定义登录成功后跳转的页面。通常也可以在自己登录认证方法里redirect到需要的页面。
unauthorizedUrl,定义未认证时显示的页面。
filterChainDefinitions,定义哪些路径应该做何种过滤策略。
anon,logout,authc这些都是shiro默认实现的过滤器filter。
anon表示可以匿名访问的路径,authc表示需要登录认证的路径
过滤器链使用最先匹配返回策略,所以我们需要把不需要认证即可访问的路径放在前面。
下面是自己实现的自定义Realm:MyRealm
1 | import org.apache.shiro.authc.AuthenticationException; |
MyRealm集成AuthorizingRealm,需要重写AuthorizingRealm的两个方法:doGetAuthorizationInfo,doGetAuthenticationInfo。
AuthorizationInfo represents a single Subject’s stored authorization data (roles, permissions, etc) used during authorization (access control) checks only.
doGetAuthorizationInfo方法返回一个AuthorizationInfo,AuthorizationInfo对象是一个单一的Subject对象,存储着用户的授权数据,只用于授权检查时使用。
doGetAuthenticationInfo方法,针对给定的用户,获取对应用户的认证数据,提供给认证用户身份时使用。
例子中UserRemote userService是提供用户数据的具体service服务。
有了数据源realm和shiro配置文件,现在开始接入shiro配置到spring容器
springboot shiro配置类:ShiroAutoConfig.java
1 | import org.apache.shiro.mgt.DefaultSecurityManager; |
这里例子里说明,我们需要提供realm和shiroFilter两个bean配置给spring容器。
getShiroFilterFactoryBean方法返回ShiroFilterFactoryBean,将会把yml里面的配置读取到ShiroFilterFactoryBean中,然后把realm设置进securityManager。
yml配置有ShiroProperties类持有并提供给ShiroFilterFactoryBean。
当然,还需要配置几个其他配置,都在ShiroManager配置好了。
ShiroProperties:
1 | import java.util.Map; |
ShiroManager:
1 | import org.apache.shiro.cache.CacheManager; |
CacheManager负责缓存,这里简单的使用默认的内存缓存管理MemoryConstrainedCacheManager。
如果需要ehcache,或者使用redis作为缓存,则只需要实现自己的CacheManager和SessionManager即可。
至此,springboot中引入shiro就完成了,其他更多具体使用方法,根据具体而定。
这里简单的提供一下在mvc的controller中做登录和登出怎么做。
LoginController: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
48import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LoginController {
@RequestMapping("/login")
@ResponseBody
public void login(HttpServletRequest req, HttpServletResponse resp, String username, String password) throws ServletException, IOException {
Subject subject = SecurityUtils.getSubject();
String error = null;
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
} catch (UnknownAccountException e) {
error = "用户名/密码错误";
} catch (IncorrectCredentialsException e) {
error = "用户名/密码错误";
} catch (AuthenticationException e) {
// 其他错误,比如锁定,如果想单独处理请单独catch处理
error = "其他错误:" + e.getMessage();
}
if (error != null) {// 出错了,返回登录页面
req.setAttribute("error", error);
resp.sendRedirect("/forbidden.html");
} else {// 登录成功
resp.sendRedirect("/index.html");// 设置跳转的页面
}
}
@RequestMapping(value = "/logout")
@ResponseBody
public void logout(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
resp.sendRedirect("/index.html");
}
}
done!