1.Shiro简简简简介
Apache Shiro™是一个功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。
可以看到Shiro的主要体系结构:
1、 Authentication 认证 ---- 用户登录
2、 Authorization 授权 --- 用户具有哪些权限
3、 Cryptography 安全数据加密
4、 Session Management 会话管理
5、 Web Integration web系统集成
6、 Interations 集成其它应用,spring、缓存框架
下面我主要讲前两个,认证和授权的用法。】
2.建立SpringBoot应用
声明:项目使用了SpringBoot、mybatis plus、RBAC的5张表(注意:我这里的系统默认一个用户只有一个角色)关于这几部门不明白的,请自行百度。
2.1项目引入的maven依赖有:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
com.yunqing
questionnaire
0.0.1-SNAPSHOT
questionnaire
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
com.baomidou
mybatis-plus-boot-starter
3.2.0
com.baomidou
mybatis-plus-generator
3.2.0
org.freemarker
freemarker
2.3.29
org.springframework.boot
spring-boot-starter-thymeleaf
org.apache.shiro
shiro-spring
1.4.1
com.github.theborakompanioni
thymeleaf-extras-shiro
2.0.0
mysql
mysql-connector-java
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
2.2创建LoginController首先令/user/index访问到主页面
package com.yunqing.questionnaire.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Created by yunqing on 2019/11/20 23:33
*/
@Controller
@RequestMapping("/user")
public class LoginController {
@GetMapping("/index")
public String index() {
return "index";
}
}
springboot默认h5页面放在templates中
image.png
这样访问/user/index就能访问到上图的index.html页面了。
3.SpringBoot整合Shiro实现用户认证
3.1 分析Shiro的核心Api
Subject: 用户主体(把操作交给SecurityManager)
SecurityManager:安全管理器(关联Realm)
Realm:Shiro连接数据的桥梁
3.2 SpringBoot整合Shiro
3.2.1自定义realm类
/**
* 自定义Realm
* Created by yunqing on 2019/11/21 21:43
*/
@Slf4j
public class UserRealm extends AuthorizingRealm {
/**
* 执行授权逻辑
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("执行授权逻辑");
}
/**
* 执行认证逻辑
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("执行认证逻辑");
}
}
3.2.2编写Shiro配置类ShiroConfig
/**
* Created by yunqing on 2019/11/21 21:39
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getRealm() {
return new UserRealm();
}
}
3.3使用Shiro内置过滤器实现页面拦截
/**
* Created by yunqing on 2019/11/21 21:39
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* 添加shiro内置过滤器
* 常用的过滤器
* anon:无需认证登录就可以访问
* authc:必须认证才能访问
* user:如果使用remeberMe的功能可以直接访问
* perms:该资源必须得到资源权限才能访问
* role:该资源必须得到角色权限才能访问
*/
Map filterMap = new LinkedHashMap<>();
filterMap.put("/user/index", "anon");//设置/user/index不需要认证
filterMap.put("/user/add", "authc");//设置/user/add需要认证
//filterMap.put("/user/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//Shiro默认需要认证跳转login.jsp,这里更改通过controller跳转到login.html
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
//shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getRealm() {
return new UserRealm();
}
}
上面的代码运行后,首先localhost:8080/user/index进入index.html我们设置了不拦截,index.html页面如下
跳转添加页面
跳转修改页面
这时候的LoginController页面也加入了这几个跳转的方法
@GetMapping("/index")
public String index() {
return "index";
}
@GetMapping("/add")
public String add() {
return "add";
}
@GetMapping("/update")
public String update() {
return "update";
}
@GetMapping("/toLogin")
public String toLogin() {
return "login";
}
如上面设置/user/add需要认证,/user/update没设置认证
3.4实现用户认证(登录)操作
3.4.1 登录页面login.html
Title
登录页面login
3.4.2 编写LoginController中的登录逻辑
@GetMapping("/login")
public String login(String username, String password, Model model) {
System.out.println(username);
/**
* 1.获取Subject
*/
Subject subject = SecurityUtils.getSubject();
/**
* 把用户名,密码封装进UsernamePasswordToken
*/
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try{
/**
* subject携带token调用login,不出异常则登陆成功
* 具体登录去ShiroConfig的认证里去决定
*/
subject.login(token);
//登陆成功
return "redirect:index";//重定向到index页面
} catch (IncorrectCredentialsException e) {
//e.printStackTrace();
//登录失败:密码错误
model.addAttribute("msg", "密码错误");
return "login";
} catch (UnknownAccountException e) {
//e.printStackTrace();
//登录失败:用户名不存在
model.addAttribute("msg", "用户名不存在");
return "login";
}
}
3.4.3编写自定义realm的认证逻辑,去数据库查询用户名密码
/**
* 执行认证逻辑
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("执行认证逻辑");
//通过authenticationToken获取当前登录用户信息
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//mybatis plus 去数据库根据登录名查询User
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("login_name", token.getUsername());
User user = userService.getOne(wrapper);
//判断是否查询到
if (StringUtils.isEmpty(user)) {
return null; //shiro内部会抛出UnknownAccountException异常
}
//判断密码,如果认证成功,则第一个参数是传给授权逻辑的,相当于授权逻辑中的Principal()
return new SimpleAuthenticationInfo(user, user.getPassWord, "");
}
登录成功后重定向到index.html再次点击跳转添加页面,成功
测试一下登录过程中用户名不存在,和密码错得情况,看看利用Model返回的msg,login.html页面的thymeleaf标签
是否接收到msg的值。
4.实现用户授权
4.1 使用shiro内置过滤器拦截资源
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* 添加shiro内置过滤器
* 常用的过滤器
* anon:无需认证登录就可以访问
* authc:必须认证才能访问
* user:如果使用remeberMe的功能可以直接访问
* perms:该资源必须得到资源权限才能访问
* role:该资源必须得到角色权限才能访问
*/
Map filterMap = new LinkedHashMap<>();
filterMap.put("/user/index", "anon");//路径/user/index不需要认证
filterMap.put("/user/login", "anon");//路径/user/login不需要认证
filterMap.put("/user/add", "perms[user:add]");//路径/user/add不仅需要登录,还需要登录的角色拥有访问授权字符串为user:add资源的权利
//filterMap.put("/user/update", "perms[user:update]");
filterMap.put("/user/*", "authc");//拦截/user/*的路径需要进行认证,不需要认证的写在上面了
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");//登录后如果没有访问权限,设置跳转noAuth.html页面
return shiroFilterFactoryBean;
}
4.2 完成资源的授权,修改UserRealm类
@Autowired
private UserService userService;
@Autowired
private UserRoleService userRoleService;
@Autowired
private RoleResourceService roleResourceService;
@Autowired
private ResourceService resourceService;
/**
* 执行授权逻辑
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("执行授权逻辑");
/**
* 给user:add进行授权
*/
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//SecurityUtils.getSubject() 获取当前user
//info.addStringPermission("user:add");
//Subject subject = SecurityUtils.getSubject();
User user = (User) principalCollection.getPrimaryPrincipal();//获取当前user
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("user_id", user.getId());
UserRole userRole = userRoleService.getOne(wrapper);//获取角色id 当前系统默认一个用户对应一个角色。
if (StringUtils.isEmpty(userRole)) return null;
String roleId = userRole.getRoleId();
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role_id", roleId);
List list = roleResourceService.list(queryWrapper);//查出角色对应的资源id
List strs = new ArrayList<>();
list.forEach(e->strs.add(e.getResourceId()));
Set flags = new HashSet<>();
strs.forEach(e->{
String flag = resourceService.getById(e).getFlag();
if (flag!=null && !"".equals(flag))
flags.add(flag);
});
info.addStringPermissions(flags);//授权当前角色可以访问的资源
return info;
}
5.设置页面只显示当前角色能访问的资源thymeleaf整合shiro
5.1 配置ShiroDialect,在ShiroConfig中配置
/**
* 使前端shiro-thymeleaf生效
* @return
*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
5.2 在页面上使用shiro标签,index.html页面
Title
注意,因为加入了shiro授权标签,所以最初访问/user/index就不会显示内容,因为没有被授权,这时候,我们可以直接通过/user/toLogin访问login页面进行登录,如下所示:数据库中admin配置了添加user:add资源授权,admin111配置了修改user:update资源授权。