SpringBoot整合Shiro详细用法

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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值