书接上回,继续认识shiro
分析QuickStar代码
3大对象:
Subject——用户
SecurityManager——管理所有用户
Realm ——连接数据
//重点
// 1.
// get the currently executing user:
//获取当前的用户对象 Subject
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
//通过当前用户拿到Session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
//2.
//判断当前的用户是否被认证
if (!currentUser.isAuthenticated()) {
//token:令牌
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//设置记住我
token.setRememberMe(true);
try {
currentUser.login(token);//执行登录操作
} catch (UnknownAccountException uae) { //未知账户
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) { //密码不对
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) { //用户锁定
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
catch (AuthenticationException ae) { //认证异常
}
//3.
//test a role: 测试角色,hasRole在SpringSecurity中也存在
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//4.
//粗粒度 test a typed permission (not instance-level)
//检测权限
if (currentUser.isPermitted("lightsaber:wield")) {...}
//细粒度 a (very powerful) Instance Level permission
if (currentUser.isPermitted("winnebago:drive:eagle5")) {...}
//5.
//all done - 退出
currentUser.logout();
//结束启动
System.exit(0);
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.1version>
dependency>
//对应上面那三大类,配置的时候从下往上
//shiroFilterFactoryBean
//DefaultWebSecurityManager
//创建realm对象
shiroConfig
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean 步骤3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//DefaultWebSecurityManager 步骤2;
// 如何将UserRealm与步骤1中的UserRealm绑定,通过Spring传递,使用@Qualifier("userRealm")
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象 ,需要自定义类(UserRealm) 步骤1
//@Bean(name="userRealm")
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
现在需要使得有的用户可访问,有的用户不能访问,进行实现
添加shiro的内置过滤器:
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
// anon:无需认证就可以访问;
// authc:必须认证了才能访问;
// user:必须有"记住我"功能才能使用;
// perms:拥有对某个资源的权限才能访问;
// role:有某个角色权限才能访问
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","anon"); //所有人都可访问add页面
filterMap.put("/user/update","authc"); //需要认证才可访问,还需自己写登陆页面,然后跳转
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求,自己写一个登陆页面在templates下
bean.setLoginUrl("/tologin");
return bean;
}
controller中增加
@RequestMapping("/tologin")
public String tologin(){
return "login";
}
测试:点击Update就跳转到登录,表明登录拦截成功!
先来实现认证,在MyController类中进行实现:
//认证工作:
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登陆数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); //执行登录方法,异常在quickstart例子中有说明
return "index";
}catch (UnknownAccountException e){ // 用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){ // 密码异常
model.addAttribute("msg","密码错误");
return "login";
}
}
在前端登录页面接收msg
<h1>登录h1>
<p th:text="${msg}" style="color: red;">p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username">p>
<p>密码:<input type="password" name="password">p>
<p><input type="submit">p>
form>
测试,点击Update跳至登录页面,当输入用户名密码错误:
且在IDEA打印:执行了UserRealm类中的认证方法
接着在UserRealm类的认证方法中添加:
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了->认证doGetAuthenticationInfo");
//用户名,密码——数据库中读取
String name = "root";
String password = "123456";
//转换token
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
if (!userToken.getUsername().equals(name)){
return null; //就会抛出异常,即用户名不存在异常
}
//密码认证,shiro自己做
return new SimpleAuthenticationInfo("",password,"");
}
进行再次测试:当输入root,123登录会显示密码错误;输入正确的用户名密码后就可显示首页
连接数据库
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.12version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.0version>
dependency>
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# springboot默认是不注入这些属性值的,需要自己绑定
# druid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMills: 60000
minEvictableIdleTimeMills: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnreturn: false
poolPrepareStatements: true
# 配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
# 如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
# 则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis.type-aliases-package=com.feng.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
@SpringBootTest
class ShiroSpringbootApplicationTests {
@Autowired
UserServiceImpl userService;
@Test
void contextLoads() {
System.out.println(userService.queryUserByName("天天"));
}
}
结果:输出User(id=5, name=天天, pwd=111)
将之前写的用户名密码现在变为从数据库中读取
//自定义的UserRealm 须继承 AuthorizingRealm 即可
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了->授权doGetAuthorizationInfo");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了->认证doGetAuthenticationInfo");
//转换token
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
//连接真实数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null;//就会抛出异常,即用户名不存在异常
}
//密码认证,shiro自己做
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}
启动后测试,输入数据库中的用户名和密码可进行登录
在ShiroFilterFactoryBean 中实现
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
//授权,正常情况下,没有授权会跳转到未授权页面
filterMap.put("/user/add","perms[user:add]"); //需要user有add权限才能访问
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/tologin");
//设置未授权的请求
bean.setUnauthorizedUrl("/noauth");
return bean;
}
在controller中控制跳转的未授权页面
//未授权页面
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未经授权无法访问此页面";
}
测试:
先登录:
在首页再访问add页面:
且控制台打印:执行了->授权doGetAuthorizationInfo,说明我们可以在这里面做些操作~
在没写未经访问页面时,点击add会报401错误,类型是未授权
继续完善程序:
在UserRealm,授权中写:
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了->授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//经过这里就可以给用户授权
info.addStringPermission("user:add");
return info;
}
启动程序测试一下:现在无论什么用户登录之后,都可以进行add访问,因为都走了doGetAuthorizationInfo这个方法,给写死了
所以,这个权限应该在数据库中,于是修改数据库,添加字段
在认证中的用户,如何拿到授权当中呢?
//认证
....
return new SimpleAuthenticationInfo(user,user.getPwd(),"")
//这第一个参数属性是principal,传进user
//在授权方法中,如何拿到?
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
整体授权方法:
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了->授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//经过这里就可以给用户授权
// info.addStringPermission("user:add");
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
//拿到User对象
User currentUser = (User) subject.getPrincipal();
//设置当前用户权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//认证
....
}
shiroConfig配置,加上update的:
//授权,正常情况下,没有授权会跳转到未授权页面
filterMap.put("/user/add","perms[user:add]"); //需要user有add权限才能访问
filterMap.put("/user/update","perms[user:update]");
启动测试:现在登录root用户,只能访问update页面,无权限访问add,登录小宋,只能访问add~
还有一个问题,登录了用户之后,首页应该只显示这个用户能访问的页面,而其他页面不可见
所需:
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
//整合shiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>首页h1>
<p>
<a th:href="@{/tologin}">登录a>
p>
<p th:text="${msg}">p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">Adda>
div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">Updatea>
div>
body>
html>
测试:
此时,登录root用户,就只会显示只能它访问的页面
登录成功之后,去除登录按钮:
在UserRealm中
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了->授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//经过这里就可以给用户授权
// info.addStringPermission("user:add");
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
//拿到User对象
User currentUser = (User) subject.getPrincipal();
//设置当前用户权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了->认证doGetAuthenticationInfo");
//转换token
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
//连接真实数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null;//就会抛出异常,即用户名不存在异常
}
//拿到此刻的session,并写属性,让前端首页做判断
Subject currentsubject = SecurityUtils.getSubject();
Session session = currentsubject.getSession();
session.setAttribute("loginUser",user);
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
index.html页面:使得用户第一次访问首页出现登录按钮,不出现注销按钮,登陆后,在页面上显示注销按钮,不显示登录按钮
<body>
<h1>首页h1>
<div th:if="${session.loginUser==null}">
<a th:href="@{/tologin}">登录a>
div>
<div th:if="${!(session.loginUser==null)}">
<a th:href="@{/logout}">注销a>
div>
<p th:text="${msg}">p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">Adda>
div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">Updatea>
div>
body>
在controller中把注销功能写:
//注销
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
进行访问测试:
第一次访问:
登录root用户:
进入属于它的页面,以及注销按钮:
点击注销:会跳转至登录页面!
到这里入门实例就结束了
后续跟进,继续更新此处笔记