1. Apache Shiro简介
1.1 什么是Apache Shiro?
-
它是一个强大灵活的强大开源安全框架,可以干净利落地处理身份认证,授权,企业会化管理和加密。
-
Apache Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
-
Apache Shiro的首要目标就是易于使用和理解,在可能的情况下掩盖复杂性,并公开一个干净直观的API,以简化开发人员确保其应用程序安全的工作。
1.2 Apache Shiro的特点:
根据官网我们可以知道:
Shiro主要关心的四个方面:
- 认证(authenticate):有时也称登录,对用户的身份进行认证。
- 授权(authorize):访问控制的过程,确定谁有权访问什么。
- 会话管理:管理特定用户的会话,即使在非Web程序或EJB的应用程序也是如此。
- 密码学: 使用加密算法确保数据安全,且容易使用。
Shiro还支持其他的功能,对于不同环境的应用程序提供支持和加强这些问题:
- Web支持,Shiro的Web支持API有助于保护Web应用程序.
- 缓存: 缓存能够确保安全操作并保持快速高效。
- 并发性: 通过并发功能支持多线程应用程序。
- 测试: 测试支持旨在帮助您编写单元和集成测试,并确保您的代码按预期受到保护。
- “记住我”: 记住用户在会话中的身份,以便他们只需要在强制登录时登录。
- 运行方式" 允许用户采用其他用户的身份(如果允许)的功能,有时在管理方案中很有用。
1.3 Apache Shiro的架构
Apache Shiro的设计目标是通过直观和易于使用来简化应用程序安全性。Apache Shiro在几乎任何应用程序中都保持直观且易于使用。
Shiro的体系结构有三个主要概念:
- 主题(Subject): 本质上是当前正在执行的用户,也可以是第三方服务,守护程序或者cron作业,与软件交互的东西。
- 安全管理器(SecurityManager): Shiro的核心,协调其内部安全组件,为任何安全操作完成所有繁重的工作。
- 连接数据(Realm):作为Shiro和应用程序安全的桥梁,用于用户身份认证和授权。Realm本质上是一个特定于安全的DAO:封装了数据源的连接详细信息,并将需要将相关数据提供给Shiro,至少需要指定一个Realm用于认证和授权。
详细架构
讲解部分属性:
- Authenticator (org.apache.shiro.authc.Authenticator) 是负责执行和响应用户身份验证(登录)尝试的组件
- 认证策略 (org.apache.shiro.authc.pam.AuthenticationStrategy) 如果配置了多个Realm,则将协调 Realm 数据库以确定身份验证尝试成功或失败的条件
- Authorizer (org.apache.shiro.authz.Authorizer) 是负责确定用户在应用程序中的访问控制的组件
- SessionManager (org.apache.shiro.session.mgt.SessionManager) 知道如何创建和管理用户生命周期,为所有环境中的用户提供强大的会话体验
- SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO) 代表 执行持久性 (CRUD) 操作
- CacheManager (org.apache.shiro.cache.CacheManager) 创建和管理其他 Shiro 组件使用的实例生命周期。
1.4 下载
进入官网: shiro官网
找到10分钟教程,然后能看到下载,
要求:
我们的电脑要有jdk1.8以上版本,Maven版本要在3.0.3+
1.5 研究quickStart
使用下载的Shiro,使用Intellij IDEA找到下载的shiro.zip包,点进samples/quickstart
查看一下这个文件配置了些什么:
-
- 导入的依赖
<dependencies>
<!-- shiro 核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</dependency>
<!-- configure logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
-
- 配置了log4j2.xml,实现了日志
<Configuration name="ConfigTest" status="ERROR" monitorInterval="5">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.apache" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="net.sf.ehcache" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.apache.shiro.util.ThreadContext" level="warn" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
-
- 多了个shiro.ini
shiro.ini是shiro的配置文件,用于生成安全管理器。
查看源码quickstart
private static final transient Logger logger = LoggerFactory.getLogger(QuickStartShiro.class);
transient 一些敏感的信息,为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输, 这些信息的变量应该加上transient关键字
//通过ini配置文件工厂获取SecurityManager对象工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//这里使用SecurityManager作为一个单例,实际上不这样做
SecurityUtils.setSecurityManager(securityManager);
通过ini配置文件来创建一个shiro安全管理器实例,ini配置文件里面包含了realms,users,roles和perssions内容。
查看一下shiro.ini
其中分为两类,一类是登录的用户【users】,一类是角色身份【roles】
【users】:包含账户,密码,角色
【roles】:包含对某些资源的权限
//在工具类中获取一个Subject,Subject可以是用户,第三方服务 ,cron
Subject currentUser = SecurityUtils.getSubject();
Security需要我们去给与一个SecurityManage,然后我们可以去通过getSubject()来获取一个当前正在执行的用户。
进行会话操作:
进行会话的时候不需要web环境:这也证实了可以在几乎任何应用程序上使用。
//可以通过用户获取一个Session对象
Session session = currentUser.getSession();
//给Session设置属性
session.setAttribute("liang","hello");
String value = (String) session.getAttribute("liang");
if(value.equals("hello"))
{
logger.info("消息为:"+value);
}
授权和认证
既然是安全性框架,自然离不开授权和认证,在Shiro中是通过令牌实现的UsernamePasswordToken
//判断用户是否被认证
if(!currentUser.isAuthenticated())
{
//定义一个认证指令
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//开启RememberMe默认为false
token.setRememberMe(true);
try {
//登录功能,以我们的认证指令为参数
currentUser.login(token);
} catch (UnknownAccountException uae) {
logger.info("用户不存在 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
logger.info("密码不正确" + token.getPrincipal());
} catch (LockedAccountException lae) {
logger.info("用户已经锁定" + token.getPrincipal());
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
通过令牌来登录,验证正在执行的用户是否认证,以及实现了记住我功能,都通过subject来实现操作
授权
判断用户是否拥有某种身份或者是有某些权利
// 若用户已经认证了,则输出认证信息
logger.info("用户"+currentUser.getPrincipal()+"成功登录了!");
// 判断用户是否有某种角色
if(currentUser.hasRole("schwartz"))
{
logger.info("你拥有schwartz身份");
} else {
logger.info("不好意思");
}
//粗粒度
if(currentUser.isPermitted("lightsaber:wield"))
{
logger.info("You may use a lightsaber ring. Use it wisely.");
} else {
logger.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//细粒度
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
logger.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
logger.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//用户注销
currentUser.logout();
进行授权操作,判断谁有权对什么进行操作
2.整合Shiro
2.1 环境准备
导入的坐标如下
<dependencies>
<!-- Thymeleaf 环境-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Web环境-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 测试环境-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 整合Shiro的第三方模块-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Mysql的Jar包-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- 数据源:Druid-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- 整合Mybaits-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- 添加shiro-thymeleaf整合包-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
2.2 导入静态文件
添加的配置文件:包含一个登录页面,以及首页,访问成功页面
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<style>
html,
body {
height: 100%;
}
body {
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
</style>
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/login}" method="post">
<img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label for="inputUsername" class="sr-only">Username</label>
<input type="username" id="inputUsername" name="username" class="form-control" th:placeholder="#{login.username}" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" name="password" class="form-control" th:placeholder="#{login.password}" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me" th:text="#{login.remember}">
</label>
</div>
<span class="mt-5 mb-3 text-muted" th:text="${msg}" style="color:red"></span>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">0 2020-2021</p>
</form>
</body>
</div>
</body>
</html>
这里记得配置i18n配置文件,类似于如下
login.btn=登录
login.password=请输入密码
login.remember=记住我
login.tip=登录界面
login.username=请输入用户名
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro = "http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Liang</title>
</head>
<body>
<h1>首页</h1>
<!-- 通过thymeleaf判断-->
<a th:href="@{/toLogin}">登录</a>
<a th:href="@{/toLogout}">注销 </a>
<a th:href="@{/user/add}">add</a>
<a th:href="@{/user/delete}">delete</a>
</body>
</html>
add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Liang</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
delete.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Liang</title>
</head>
<body>
<h1>delete</h1>
</body>
</html>
2.3编写Shiro核心代码
- 自定义Realm类
在com.liang.config包下创建一个UserRealm类
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("进入授权信息阶段");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("进入认证信息阶段");
return null;
}
需要使用继承AuthorizingRealm,重写授权和认证两个方法。
- Shiro配置文件的类
在com.liang.config包下创建一个ShiroConfig来编写Shiro的配置
代码:
@Configuration
public class ShiroConfig {
//定义拦截的东西
//ShiroFilterFactoryBean :(3)
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager1") DefaultWebSecurityManager securityManager)
{
//创建一个ShiroFilterFactoryBean对象
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager(2)
@Bean(name ="securityManager1")
public DefaultWebSecurityManager webSecurityManager(@Qualifier("userRealm")UserRealm realm)
{
//创建默认网络安全管理器对象
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//这是一个RealmSecurityRealm对象
securityManager.setRealm(realm);
return securityManager;
}
//Real对象,连接数据 //自定义Real对象,需要继承AuthorizingRealm(1)
@Bean("userRealm")
public UserRealm userRealm()
{
return new UserRealm();
}
//整合ShiroDialect : 用来整合shiro-thymeleaf
@Bean
public ShiroDialect shiroDialect()
{
return new ShiroDialect();
}
}
- 编写Controller类
在com.liang.controller包下创建一个ShiroController类
@Slf4j
@Controller
public class ShiroController {
@RequestMapping("/user/add")
public String add()
{
return "add";
}
@RequestMapping("/user/delete")
public String delete()
{
return "delete";
}
//登录页面
@RequestMapping("/toLogin")
public String toLogin()
{
return "login";
}
}
此时项目基本操作已经搭建好啦,但没有进行认证和授权操作
2.4 Shiro整合Mybaits
1.编写application.yaml
spring:
datasource:
username: root
password: root
url : jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: 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.liang.pojo
mapper-locations: classpath:mapper/*.xml
2.编写pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String pwd;
private String perms; //权限
}
4.编写Mapper
Mapper接口
package com.liang.mapper;
import com.liang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface UserMapper {
//获取指定用户
User getUserByID(int id );
//获取全部用户
List<User> getUsers();
//添加用户
int addUser(User user);
//删除用户
int deleteUser(int id);
//更新用户
int updateUser(User user);
//通过名字查询
User getUserByName(String name);
}
Mapper接口
<?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.liang.mapper.UserMapper">
<!-- SQL片段-->
<sql id="items">
id,name,pwd,perms
</sql>
<!-- 去除多余的,号-->
<sql id="key">
<trim suffixOverrides=",">
<if test="id!=0">
id,
</if>
<if test="name!=null and name!=''">
name,
</if>
<if test="pwd!=null and pwd!=''">
pwd,
</if>
<if test="perms!=null and perms!=''">
perms,
</if>
</trim>
</sql>
<!-- 用于插入,更加灵活-->
<sql id="value">
<trim suffixOverrides=",">
<if test="id!=0">
#{id},
</if>
<if test="name!=null and name!=''">
#{name},
</if>
<if test="pwd!=null and pwd!=''">
#{pwd},
</if>
<if test="perms!=null and perms!=''">
#{perms},
</if>
</trim>
</sql>
<!-- 查询用户-->
<select id="getUserByID" resultType="User">
select <include refid="items"></include> from user where id = #{id}
</select>
<select id="getUserByName" resultType="User">
select <include refid="items"></include> from user where name = #{name}
</select>
<select id="getUsers" resultType="User">
select <include refid="items"></include> from user
</select>
<!--添加用户-->
<insert id="add" parameterType="User" >
insert into `article`(<include refid="key"></include>)
values(<include refid="value"></include>)
</insert>
<!-- 更新用户-->
<update id="updateUser" parameterType="User" >
update user
<set>
<if test="name!=null and name!=''">
name=#{name},
</if>
<if test="pwd!=null and pwd!=null">
pwd=#{pwd},
</if>
<if test="perms!=null and perms!=null">
perms=#{perms},
</if>
</set>
<where>
<if test="id!=0">
id=#{id}
</if>
</where>
</update>
<!-- 删除用户 -->
<delete id="deleteUser" parameterType="int">
delete from user
<where>
<if test="id!=0">
id=#{id}
</if>
</where>
</delete>
</mapper>
4.编写service
service接口
package com.liang.service;
import com.liang.pojo.User;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public interface UserService {
//获取指定用户
public User getUserByID(int id );
//获取全部用户
public List<User> getUsers();
//添加用户
public int addUser(User user);
//删除用户
public int deleteUser(int id);
//更新用户
public int updateUser(User user);
//通过名字查询
public User getUserByName(String name);
}
service实现类
package com.liang.service;
import com.liang.mapper.UserMapper;
import com.liang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper mapper;
@Override
public User getUserByID(int id) {
return mapper.getUserByID(id);
}
@Override
public List<User> getUsers() {
return mapper.getUsers();
}
@Override
public int addUser(User user) {
return mapper.addUser(user);
}
@Override
public int deleteUser(int id) {
return mapper.deleteUser(id);
}
@Override
public int updateUser(User user) {
return mapper.updateUser(user);
}
@Override
public User getUserByName(String name) {
return mapper.getUserByName(name);
}
}
通过测试认证看是否能够运行成功,数据库文件需自行编写。
2.5 认证和授权
编写Shiro核心文件
添加访问过滤条件
//定义拦截的东西
//ShiroFilterFactoryBean :(3)
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager1") DefaultWebSecurityManager securityManager)
{
//创建一个ShiroFilterFactoryBean对象
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
System.out.println(shiroFilterFactoryBean.isSingleton());
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro的内置过滤器
/*
anon : 无须认证就可以访问
authc: 必须认证了才可以访问
user: 只有记住我才可以访问
perms: 拥有某种资源的权限才可以访问
roles: 拥有某种角色才可以访问
*/
//过滤请求 这里我们使用字符串来表达我们的拦截规则,
Map<String, String> map = new LinkedHashMap<>();
// map.put("/user/add","authc");
// map.put("/user/delete","authc");
//用户拥有某些资源的权限才可以访问,未登录保证调到控制页面
map.put("/user/add","perms[user:add]");
map.put("/user/delete","perms[user:delete]");
//只有认证了才可以登录
map.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//设置首页
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//设置登录成功页面
shiroFilterFactoryBean.setSuccessUrl("/index");
//设置未经授权不可访问跳转的页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
return shiroFilterFactoryBean;
}
在ShiroFilterFactoryBean中添加Shiro过滤器过滤的条件,对资源的访问进行认证和权限要求,通过setFilterChainDefinitionMa()
进行设置。
编写Controller
//登录请求
@RequestMapping("/login")
public String login(String username, String password,Model model)
{
//从securityUtils下获取一个Subject对象,表示当前用户
Subject subject = SecurityUtils.getSubject();
//定义一个令牌
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
//用户登录的时候需要令牌
subject.login(token);
}catch (UnknownAccountException e) //用户不存在
{
log.info("用户名错误");
model.addAttribute("msg","用户名不存在");
return "login";
}catch (IncorrectCredentialsException e)
{
log.info("密码错误");
model.addAttribute("msg","密码错误");
return "login";
} catch (LockedAccountException lae ) {
log.info("账号是被锁定的");
model.addAttribute("msg","账号是被锁定的");
return "login";
}
return "/index";
}
通过当前执行用户进行令牌登录,来认证登录请求,交给Realm去验证处理
Realm认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("进入认证信息阶段");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//查询数据库中是否有此用户
User user = userService.getUserByName(token.getUsername());
if(user == null)
{
return null;
}
token.setRememberMe(true); //令牌记住我
//获得当前用户
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
//给当前Session存一个对象
session.setAttribute("loginUser",user);
//AuthenticationInfo 是接口 ,我们返回它的实现类 SimpleAuthenticationInfo是用来简单认证信息的
//principal获取当前用户的认证 ,credentials 密码的对象 realmName 认证名
//可以加密,默认为简单加密 使用md5盐值加密
// SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("",user.getPwd(),"");
//添加user认证信息,方便我们授权操作
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPwd(),"");
return simpleAuthenticationInfo;
}
通过验证账户是否存在,从数据库中取,然后密码交给Shiro来处理
Realm授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("进入授权信息阶段");
//权限操作
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加准许权限,硬编码
// info.addStringPermission("user:add");
//从数据库中取,需要获取数据库的对象
Subject subject = SecurityUtils.getSubject();
//获取认真信息
User currentUser = (User) subject.getPrincipal();
//设置相应查询用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}
进行访问资源认证,来判断用户是否有某种资源权限,权限从数据库中取
Controller实现无法访问资源的请求和注销请求
//权限不够页面
@RequestMapping("/noAuth")
@ResponseBody
public String unauthorized()
{
return "未授权无法访问!";
}
//注销
@RequestMapping("/toLogout")
public String loginOut()
{
//得到当前用户
Subject subject = SecurityUtils.getSubject();
//注销
subject.logout();
return "login";
}
实现用户注销,用户需要重新登录才能访问,提供了更友好的无法访问的页面
2.6 Shiro整合Thymeleaf
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro = "http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Liang</title>
</head>
<body>
<h1>首页</h1>
<!-- 通过thymeleaf判断-->
<div th:if="session.loginUser==null">
<a th:href="@{/toLogin}">登录</a>
</div>
<div th:if="session.loginUser!=null">
<a th:href="@{/toLogout}">注销 </a>
</div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPerssion="user:delete">
<a th:href="@{/user/delete}">delete</a>
</div>
</body>
</html>
Shiro整合Thymeleaf,需要添加如下标记xmlns:shiro = "http://www.thymeleaf.org/thymeleaf-extras-shiro"
,实现更好的体验感,给出客户能看见的页面,无关的页面不给出。