shiro简介
Apache Shiro™是一个功能强大且易于使用的Java安全框架,用于执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。
shiro四个核心模块
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
shrio三大核心组件
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
SecurityManager:相 当 于 SpringMVC 中 的 DispatcherServlet
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
shiro实现原理
应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager; 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
Shiro入门案例
1、使用骨架创建maven项目
2、在pom.xml中导入相关依赖
<!--shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<!--log-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
3、编写shiro.ini配置文件
[users]
admin=123,admin,emp
zhangsan=123,seller
lisi=123,emp
#三个用户admin,zhangsan,lisi
#三个角色admin(管理员),seller(经理),emp(普通员工)
#以用户管理来做对应的权限user:
[roles]
admin=*
seller=user:*
emp=user:query
4、测试类
public class ShiroTest {
public static void main(String[] args) {
//获取SecurityManager工厂,需要借助shiro.ini配置文件来初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过SecurityManager工厂拿到SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//把securityManager实例托管到SecurityUtiles工具类中
SecurityUtils.setSecurityManager(securityManager);
//通过SecurityUtils工具类获得Subject
Subject subject = SecurityUtils.getSubject();
//判断用户是否已经登陆
if(!subject.isAuthenticated()){
//构建身份令牌
UsernamePasswordToken token = new UsernamePasswordToken("lisi","123");
//登陆,没有返回值,反正没报错就登陆成功,如果报错就登陆失败
/*
UnknownAccountException:用户名不存在
IncorrectCredentialsException:密码错误
*/
try{
subject.login(token);
System.out.println("是否登陆:"+subject.isAuthenticated());
System.out.println("欢迎:"+subject.getPrincipal());
//角色校验
System.out.println(subject.hasRole("seller"));
//权限校验
System.out.println(subject.isPermitted("user:query"));
//退出登陆,用户登陆信息,会话信息,角色信息,权限信息等,全部清除
subject.logout();
}catch(UnknownAccountException e){
System.out.println("用户名不存在,请检查用户名!");
}catch(IncorrectCredentialsException e){
System.out.println("密码错误,请检查密码!");
}
}
}
}
Web
1、在pom.xml中导入相关依赖
<dependencies>
<!--servlet-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!--springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<!--log-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<!--添加tomcat插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>
2、编写springmvc-servlet.xml核心配置文件
<!--开启注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--组件扫描-->
<context:component-scan base-package="cn.bdqn.controller"/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
3、在web.xml中配置前端控制器
<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
4、在web.xml中配置ShiroFilter过滤器
<!--ShiroFilter过滤器,在启动项目的时候,会去初始化Shiro环境
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--在启动项目的时候,会去加载web-info或者classpath下的shiro.ini配置文件,
然后构建WebSecurityManager
-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
5、自定义异常解析器
因为登录失败没有返回值,直接抛出异常,对用户来说十分不友好
/*自定义异常解析器*/
public class MyExceptionResolver implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
e.printStackTrace();
ModelAndView mv = new ModelAndView();
if(e instanceof UnknownAccountException || e instanceof IncorrectCredentialsException){
mv.setViewName("redirect:/user/login");
}
return mv;
}
}
在springmvc-servlet.xml中注册该异常解析器
<bean class="cn.bdqn.resolver.MyExceptionResolver"></bean>
6、控制器
@Controller
@RequestMapping("/user")
public class UserController {
//去登录页面
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String gotoLogin(){
return "login";
}
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(User user){
/*
在web.xml中配置了shiroFilter之后,程序启动的时候就已经帮我们把shiro的环境
构建好了,就是意味着SecurityManager实例已经构建,并且成功托管到了SecurityUtils工具类中
*/
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
subject.login(token);
return "main";
}
}
7、编写shiro.ini配置文件
[users]
admin=123,admin,emp
zhangsan=123,seller
lisi=123,emp
#三个用户admin,zhangsan,lisi
#三个角色admin(管理员),seller(经理),emp(普通员工)
#以用户管理来做对应的权限user:
[roles]
admin=*
seller=user:*
emp=user:query
[main]
#如果没有登录,则跳转到这个路径
shiro.loginUrl = /user/login
#如果角色或者权限不足,则跳转到这个路径
shiro.unauthorizedUrl = /user/error
#退出之后跳转的路径
shiro.redirectUrl = /user/login
[urls]
#路径 = 过滤器别名
#不登录也可以访问/user/login这个路径
/user/login = anon
#/user/main这个路径一定是要登录才能访问
/user/main = authc
#/user/delete这个路径,只有管理员或者经理才可以访问
/user/delete = authc,roles["admin"]
#/user/add这个路径,只有具备user:add权限才可以访问
/user/add = authc,perms["user:add"]
#执行/user/logout这个路径就执行了退出操作
/user/logout = logout
shiro标签
类似JSTL中的c:if,类型下面这种效果:
<c:if test="${user.role=='管理员'}">
查看日志
</c:if>
以前用JSTL之前需要先在页页面引入JSTL标准标签库。现在使用Shiro的标签也是一样需要引入shiro标签库
<%--引入shiro标签库--%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
常用标签
1、<shiro:guest> 游客,没有记住我
2、<shiro:authenticated> 已经登陆
3、<shiro:notAuthenticated> 未登陆
4、<shiro:user> 已经登陆,或者记住我
5、<shiro:principal> 获取用户登陆名
6、<shiro:hasRole name="role_name"> 判断是指定角色
7、<shiro:lacksRole name="role_name"> 判断不是指定角色
8、<shiro:hasAnyRoles name="role_name1,role_name2"> 判断是否是其中一个角色
9、<shiro:hasPermission name="permission_name"> 判断是指定权限
10、<shiro:lacksPermisssion name="permission_name"> 判断不是指定角色
shiro加密
明文:用户输入的密码是啥,数据库存的就是啥,没有加密的就是明文
密文:用户输入的密码经过加密之后存储到数据中,加密了的数据就是密文
可逆加密:明文加密之后变成了密文,根据密文可以推算出明文,这就是可逆加密。不是很安全。
不可逆加密:明文加密之后变成了密文,无法从密文推算出明文。相对来说比较安全,建议使用不可逆加密。
shiro支持hash加密,md5和sha就是最常用的不可逆加密方式了
public class JmTest {
public static void main(String[] args) {
//明文密码
String pwd = "123";
/*
.toString():十进制,相对base64来说密文长一些
.toBase64():base64的编码格式,相对toString()来说密文短一些,建议使用
* */
//方式一,直接加密,手段太少,相对来说不是太安全
String pwd1 = new Md5Hash(pwd).toString();
System.out.println("明文是:"+pwd+",密文是:"+pwd1);
String pwd2 = new Md5Hash(pwd).toBase64();
System.out.println("明文是:"+pwd+",密文是:"+pwd2);
//方式二,在明文密码的基础之上加点佐料(盐),来提高明文密码的复杂度
String salt = UUID.randomUUID().toString(); //盐
String pwd3 = new Md5Hash(pwd,salt).toBase64();
System.out.println("明文是:"+pwd+",密文是:"+pwd3);
/*类似下面的效果
pwd += salt;
String p1 = new Md5Hash(pwd).toBase64();*/
//方式三,在方式二的基础之上循环加密,从而提高加密的复杂度,建议使用
int count = 1000; //循环加密次数
String pwd4 = new Md5Hash(pwd,salt,count).toBase64();
System.out.println("明文是:"+pwd+",密文是:"+pwd4);
/*类型下面的效果
pwd += salt; //第一次加盐
String p1 = new Md5Hash(pwd).toBase64(); //第一次加密
p1 += salt; //第二次加盐
String p2 = new Md5Hash(p1).toBase64(); //第二次加密
p2 += salt; //第三次加盐
String p3 = new Md5Hash(p2).toBase64(); //第三次加密
一直循环1000次*/
/*sha加密,跟MD5加密的写法是一样的,具体用哪个加密,自己决定*/
String pwd5 = new Sha512Hash(pwd,salt,count).toBase64();
System.out.println("明文是:"+pwd+",Sha密文是:"+pwd5);
String pwd6 = new Sha256Hash(pwd,salt,count).toBase64();
System.out.println("明文是:"+pwd+",Sha密文是:"+pwd6);
}
}
SpringBoot+shiro整合
springboot+mybatisPlus+thymeleaf+shiro加密、认证、授权
数据库
drop table if exists t_user;
create table t_user(
user_id int primary key auto_increment,
username varchar(20) not null unique,
password varchar(255) not null,
salt varchar(255) not null
);
insert into t_user(username,password,salt) values("xc","123","1235");
drop table if exists t_role;
create table t_role(
role_id int primary key auto_increment,
role_name varchar(20) not null
);
insert into t_role(role_name) values("seller");
drop table if exists t_permission;
create table t_permission(
permission_id int primary key auto_increment,
permission_name varchar(50) not null
);
insert into t_permission(permission_name) values("user:query"),("user:add"),("user:delete");
drop table if exists t_user_role;
create table t_user_role(
user_id int references t_user(user_id),
role_id int references t_role(role_id)
);
insert into t_user_role(user_id, role_id) VALUES (1,1);
drop table if exists t_role_permission;
create table t_role_permission(
role_id int references t_role(role_id),
permission_id int references t_permission(permission_id)
);
insert into t_role_permission(role_id,permission_id) values(1,1),(1,2),(1,3);
--认证,根据用户名查找对应的用户信息
select * from t_user where username="xc";
--授权,根据用户名查找对应的角色名
select role_name from t_role r
inner join t_user_role ur on r.role_id=ur.role_id
inner join t_user u on u.user_id=ur.user_id
where u.username="xc";
--授权,根据用户名查找对应的权限名
select permission_name from t_permission p
inner join t_role_permission rp on p.permission_id=rp.permission_id
inner join t_role r on r.role_id=rp.role_id
inner join t_user_role ur on r.role_id=ur.role_id
inner join t_user u on u.user_id=ur.user_id
where u.username="xc";
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<!--mybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!--shiro依赖,也是需要shiro-web和shiro-core
shiro-spring需要依赖上面得两个jar包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
application.properties
#数据库连接参数
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/yjhdb
spring.datasource.username=root
spring.datasource.password=1234
spring.thymeleaf.cache=false
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
@TableId("user_id")
private Integer userId;
private String username;
private String password;
private String salt;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_role")
public class Role {
@TableId("role_id")
private Integer roleId;
@TableField("role_name")
private String roleName;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_permission")
public class Permission {
@TableId("permission_id")
private Integer permissionId;
@TableField("permission_name")
private String permissionName;
}
数据访问层
@Mapper
public interface UserDao extends BaseMapper<User> {
}
@Mapper
public interface RoleDao extends BaseMapper<Role> {
@Select("select role_name from t_role r " +
"inner join t_user_role ur on r.role_id=ur.role_id " +
"inner join t_user u on u.user_id=ur.user_id " +
"where u.username=#{username}")
public Set<String> findByUsername(@Param("username") String username);
}
@Mapper
public interface PermissionDao {
@Select("select permission_name from t_permission p " +
"inner join t_role_permission rp on p.permission_id=rp.permission_id " +
"inner join t_role r on r.role_id=rp.role_id " +
"inner join t_user_role ur on r.role_id=ur.role_id " +
"inner join t_user u on u.user_id=ur.user_id " +
"where u.username=#{username}")
public Set<String> findByUsername(@Param("username") String username);
}
业务逻辑层
public interface UserService {
//根据用户名查询对应的用户信息
public User findByUsername(String username);
//注册
public int add(User user);
}
public interface RoleService {
//根据用户名查询对应的角色名
public Set<String> findByUsername(String username);
}
public interface PermissionService {
//根据用户名查询对应的权限名
public Set<String> findByUsername(String username);
}
实现类
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public User findByUsername(String username) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
return userDao.selectOne(queryWrapper);
}
@Override
public int add(User user) {
//盐
String salt = UUID.randomUUID().toString();
user.setSalt(salt);
//加密
String password = new Md5Hash(user.getPassword(),salt, SpringTools.SHIRO_JM_COUNT).toBase64();
user.setPassword(password);
return userDao.insert(user);
}
}
@Service
public class RoleServiceImpl implements RoleService {
@Resource
private RoleDao roleDao;
@Override
public Set<String> findByUsername(String username) {
return roleDao.findByUsername(username);
}
}
@Service
public class PermissionServiceImpl implements PermissionService {
@Resource
private PermissionDao permissionDao;
@Override
public Set<String> findByUsername(String username) {
return permissionDao.findByUsername(username);
}
}
自定义Realm
SecurityManager默认的Realm是IniRealm,这个Realm只能去加载shiro.ini文件中的数据,但是这个项目中并没有这个文件。而是把这个文件的用户,角色,权限信息都存入到了数据库中,所以IniRealm就已经没有意义了。需要自己定义一个去查询数据库的Realm,然后注入给SecurityManager使用。
/**
* 自定义Realm
*/
public class MyRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Resource
private RoleService roleService;
@Resource
private PermissionService permissionService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//做授权,肯定需要先知道是谁登录了
String username = (String)principals.getPrimaryPrincipal();
//调用service层,根据用户名查询对应的角色和权限的方法
Set<String> roles = roleService.findByUsername(username);
Set<String> permissions = permissionService.findByUsername(username);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户登录输入的用户名
String username = (String)token.getPrincipal();
//调用service层根据用户名查询对应的用户的方法
User user = userService.findByUsername(username);
if(user == null){
return null;
}
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());;
}
}
Shiro配置类
/**
* Shiro配置类
*/
@Configuration
public class ShiroConfig {
/*注册密码比对器(什么方式加密,迭代多次,是否采用十进制)*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密方式
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置加密迭代次数
hashedCredentialsMatcher.setHashIterations(SpringTools.SHIRO_JM_COUNT);
//是否采用十进制
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
return hashedCredentialsMatcher;
}
/*注册自定义Realm,加入到bean容器*/
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
//把自定义的密码比对器注册给Realm
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myRealm;
}
/*配置SecurityManager*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//把自定义Realm注入给SecurityManager
securityManager.setRealm(myRealm());
return securityManager;
}
/*Filter工厂,设置对应的过滤条件和跳转条件*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String,String> map = new HashMap<String,String>();
//执行/user/logout这个路径就执行了退出操作
map.put("/user/logout","logout");
//不登录也可以访问/user/login这个路径
map.put("/user/login","anon");
//不登录也可以访问/user/add这个路径
map.put("/user/add","anon");
///user/main这个路径一定是要登录才能访问
map.put("/user/main","authc");
///user/delete这个路径,只有经理才可以访问
map.put("/user/delete","roles[seller]");
//...
//配置未登录跳转的路径
shiroFilterFactoryBean.setLoginUrl("/user/login");
//角色和权限不足时跳转的路径
shiroFilterFactoryBean.setUnauthorizedUrl("/user/error");
//退出跳转的路径
shiroFilterFactoryBean.setSuccessUrl("/user/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/*注册自定义异常解析器*/
@Bean
public MyExceptionResolver myExceptionResolver(){
return new MyExceptionResolver();
}
}
控制器
@Controller
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
/*跳转去注册页面*/
@GetMapping("add")
public String add(){
return "add";
}
/*注册功能*/
@PostMapping("add")
public String add(User user){
userService.add(user);
return "redirect:/user/login";
}
/*跳转去登录页面*/
@GetMapping("/login")
public String login(){
return "login";
}
/*登录功能*/
@PostMapping("/login")
public String login(User user){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
subject.login(token);
return "redirect:/user/main";
}
/*跳转去main页面*/
@GetMapping("/main")
public String main(){
return "main";
}
/*删除操作*/
@GetMapping("/delete")
@ResponseBody
public String delete(){
return "删除成功";
}
}
页面模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>欢迎登录</h1>
<form action="login" method="post">
<p>用户名:<input name="username"></p>
<p>密 码:<input type="password" name="password"></p>
<p><input type="submit" value="登录"></p>
</form>
</body>
</html>
记住我
在登录之后,把用户的信息记录在Cookie中。以后访问,可以不登录,但是服务器也能识别出来。如果需要执行一些敏感的操作再要求用户登录即可,用户体验好。
如果需要使用记住我,只需要在登录之前开启记住我的功能即可
/*登录功能*/
@PostMapping("/login")
public String login(User user){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
//记住我
token.setRememberMe(true);
subject.login(token);
return "redirect:/user/main";
}
默认Cookie的最大过期时间是365天。而且里面有两个同名的Cookie(RememberMe),其中value为deleteMe的cookie是为了登录的时候先把原来存在的Cookie删除,在写入最新的Cookie,保存每次登录之后Cookie都是最新的
如果你的项目中就是使用默认的Cookie那就已经完成了。但是如果你想对默认的Cookie做修改,就要自己配置了,比如最大过期时间不想那么长
/*自定义Cookie配置*/
@Bean
public SimpleCookie simpleCookie(){
//创建名为rememberMe的Cookie
SimpleCookie cookie = new SimpleCookie("rememberMe");
//设置cookie的最大过期时间,单位是秒,10天=864000秒
cookie.setMaxAge(864000);
//设置只能http请求访问,默认就是true
cookie.setHttpOnly(true);
return cookie;
}
/*cookie管理对象,rememberMe管理器*/
@Bean
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
//给CookieRememberMeManager注入自定义的Cookie
cookieRememberMeManager.setCookie(simpleCookie());
return cookieRememberMeManager;
}
/*配置SecurityManager*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//把自定义Realm注入给SecurityManager
securityManager.setRealm(myRealm());
//把自定义rememberMe管理器注入给SecurityManager
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
记住我前端标签
thymeleaf模板默认不支持shiro标签的,如果需要在thymeleaf中使用shiro标签,需要额外引入对应的依赖
1、pom.xml
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>1.2.1</version>
</dependency>
2、在shiro配置文件中配置方言
/*配置shiro方言,让thymeleaf支持shiro标签*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
3、页面使用
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>个人中心</title>
</head>
<body>
<!--游客模块-->
<p shiro:guest="">
你好,游客!
</p>
<!--已登录模块-->
<p shiro:authenticated="">
欢迎:<span shiro:principal=""></span>
</p>
<!--已经登录或者记住我-->
<p shiro:user="">
我记住你了:<span shiro:principal=""></span>
</p>
</body>
</html>
Session会话管理
shiro是一个安全框架,所以对状态的保持是必须的。shiro提供了一整套session管理方案
核心对象
1、SimpleSession:负责完成session基本功能
2、SimpleSessionFactory:负责生产SimpleSession的工厂
3、SessionDAO:类似UserDAO,负责session的增删改查
4、DefaultSessionManager:session管理者,管理SimpleSessionFactory和SessionDAO
Session参数修改
/*自定义session-Cookie配置*/
@Bean
public SimpleCookie simpleCookie1(){
//创建名为rememberMe的Cookie
SimpleCookie cookie = new SimpleCookie("JSESSIONID");
//设置cookie的最大过期时间,单位是秒,默认-1,表示当初会话有效。0表示删除当前cookie
cookie.setMaxAge(-1);
//设置只能http请求访问,默认就是true
cookie.setHttpOnly(true);
return cookie;
}
/*session管理器*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(){
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
//给DefaultWebSessionManager注入自定义的Cookie
defaultWebSessionManager.setSessionIdCookie(simpleCookie1());
//session全局超时时间,默认30分钟,单位毫秒。10000L=10秒
defaultWebSessionManager.setGlobalSessionTimeout(10000L);
return defaultWebSessionManager;
}
/*配置SecurityManager*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//把自定义Realm注入给SecurityManager
securityManager.setRealm(myRealm());
//把自定义rememberMe管理器注入给SecurityManager
securityManager.setRememberMeManager(rememberMeManager());
//把自定义session管理器注入给SecurityManager
securityManager.setSessionManager(defaultWebSessionManager());
return securityManager;
}
Session监听
session监听就是监控session的3个状态,创建,过期,停止
过期:判断过期的方式是在session被创建的时候记录创建时间,然后跟现在的时间对比,判断这个session是否已经过期。session不会自动报告过期,需要再次访问或者检测器检测时才会识别这个session是否已经过期
停止:只有在logout或session.stop()时才会触发
/**
* 自定义session监听器
*/
public class MySessionListener extends SessionListenerAdapter {
//session创建时触发
@Override
public void onStart(Session session) {
System.out.println("--session创建--");
}
//session停止时触发
@Override
public void onStop(Session session) {
System.out.println("--session停止--");
}
//session过期时触发
@Override
public void onExpiration(Session session) {
System.out.println("--session过期--");
}
}
在shiro配置类中添加相关配置
/*注册自定义sessino监听器*/
@Bean
public List<SessionListener> sessionListener(){
List<SessionListener> listeners = new ArrayList<>();
listeners.add(new MySessionListener());
//...如果有多个监听器,直接往listeners集合中添加就可以了
return listeners;
}
/*session管理器*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(){
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
//给DefaultWebSessionManager注入自定义的Cookie
defaultWebSessionManager.setSessionIdCookie(simpleCookie1());
//session全局超时时间,默认30分钟,单位毫秒。10000L=10秒
defaultWebSessionManager.setGlobalSessionTimeout(10000L);
//给session管理者注入自定义session监听器
defaultWebSessionManager.setSessionListeners(sessionListener());
return defaultWebSessionManager;
}
Session检测
用户直接关闭浏览器,session是否过期无法得知,意味着session就不能停止。所以shiro提供了session的检测机制,可以定时发起检测,识别session是否过期。session检测是默认开启的,默认时间是1小时,所以只需要修改检测的时间间隔就可以了。
/*session管理器*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(){
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
//给DefaultWebSessionManager注入自定义的Cookie
defaultWebSessionManager.setSessionIdCookie(simpleCookie1());
//session全局超时时间,默认30分钟,单位毫秒。10000L=10秒
defaultWebSessionManager.setGlobalSessionTimeout(10000L);
//给session管理者注入自定义session监听器
defaultWebSessionManager.setSessionListeners(sessionListener());
//开启session检测,默认开启,可以不写
defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
//设置检测的时间间隔,单位毫秒,默认1小时,15000L=15秒
defaultWebSessionManager.setSessionValidationInterval(15000L);
return defaultWebSessionManager;
}
Shiro注解开发
1、@RequiresAuthenthentication:表示当前Subject已经通过login进行身份验证;即 Subject.isAuthenticated()返回 true
2、@RequiresUser:表示当前Subject已经身份验证或者通过记住我登录的
3、@RequiresGuest:表示当前Subject没有身份验证或者通过记住我登录过,即是游客身份
4、@RequiresRoles(value = {"admin","user"},logical = Logical.AND):表示当前Subject需要角色admin和user
5、@RequiresPermissions(value = {"user:delete","user:b"},logical = Logical.OR):表示当前Subject需要权限user:delete或者user:b
注解就是就是一个标识,不会自动生效,所以要使用注解,需要做一些配置
/*开启shiro注解,借助aop来实现*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
//开启shiro注解
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/*开启aop注解支持*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
//给AuthorizationAttributeSourceAdvisor注入SecurityManager
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
shiro+redis
pom.xml
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--shiro+redis缓存插件-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
<!--工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
application.properties
#redis相关配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-idle=8
spring.redis.timeout=10000
ShiroConfig.java
/*注入redis参数*/
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
/*配置shiro的redis管理器*/
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
return redisManager;
}
/*配置shiro的redis缓存管理器*/
@Bean
public RedisCacheManager cacheManager(){
RedisCacheManager cacheManager = new RedisCacheManager();
//给RedisCacheManager注入自定义的redis管理器
cacheManager.setRedisManager(redisManager());
return cacheManager;
}
/*配置shiro的redisSessionDAO*/
@Bean
public RedisSessionDAO redisSessionDAO(){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
//给RedisSessionDAO注入自定义的redis管理器
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/*session管理器*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(){
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
//给DefaultWebSessionManager注入自定义的Cookie
defaultWebSessionManager.setSessionIdCookie(simpleCookie1());
//session全局超时时间,默认30分钟,单位毫秒。10000L=10秒
defaultWebSessionManager.setGlobalSessionTimeout(10000L);
//给session管理者注入自定义session监听器
defaultWebSessionManager.setSessionListeners(sessionListener());
//开启session检测,默认开启,可以不写
defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
//设置检测的时间间隔,单位毫秒,默认1小时,15000L=15秒
defaultWebSessionManager.setSessionValidationInterval(15000L);
//给DefaultWebSessionManager注入自定义的redisSessionDAO
defaultWebSessionManager.setSessionDAO(redisSessionDAO());
return defaultWebSessionManager;
}
1701

被折叠的 条评论
为什么被折叠?



