springBoot整合Shiro, 进阶教程

通过上一篇Shiro基础教程 https://blog.csdn.net/I_No_dream/article/details/92799372相信大家已经能够完成简单的认证了. 接下来我们分析以下Shiro的核心API

分析Shiro的核心API

  • Suject: 用户主体(认证, 授权等方法)
  • SecurityManager: 安全管理器
  • Realm: Shiro连接数据库的桥梁

这三个核心的API他们存在一种关系, Subject关联SecurityManager, 把操作交给SecurityManager, SecurityManager关联Realm, 将认证信息或授权信息进行与数据库或缓存的连接认证

目录

在这里插入图片描述

pom.xml文件


        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <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>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </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>
        </dependency>

yml文件

spring:
  datasource:
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
mybatis:
  configuration:
    auto-mapping-behavior: full
  mapper-locations: classpath*:mapper/**/*.xml
  type-aliases-package: com.lijiajun.entity

common下的shiro包

shiroConfig类

@Configuration
public class ShiroConfig {
    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean(name = "factoryBean")
    public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        //设置安全管理  这个属性是必须的。
        factoryBean.setSecurityManager(securityManager);
        /*
        Shiro内置过滤器, 可以实现权限相关的拦截器
        常用的过滤器:
           anon:表示可以匿名使用。 
           authc:表示需要认证(登录)才能使用,没有参数 
           roles:参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"]
           每个参数通过才算通过,相当于hasAllRoles()方法。 
           perms:参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"]
           当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。 
           rest:根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。 
           port:当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等
           serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。 
           authcBasic:没有参数表示httpBasic认证 
           ssl:表示安全的url请求,协议为https 
           user:当登入操作时不做检查
         */
//        有序map
        Map<String,String> filterMap = new LinkedHashMap<>();
//        拦截请求,未登录不可访问
        filterMap.put("/userDelete","authc");
        filterMap.put("/userAdd","authc");
        filterMap.put("/userInfo","authc");
//        放行请求
//        filterMap.put("/test","anon");
//        filterMap.put("/login","anon");

        //授权过滤器  注意: 当授权拦截就, shiro会自动跳转到未授权的页面
//        filterMap.put("/add","perms[user:add]");
//        filterMap.put("/update","perms[user:update]");

//        登出操作
        filterMap.put("/logout", "logout");

//        统一过滤  注意 这个需要写在最后
//        filterMap.put("/*","authc");

        /*
        loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性
                    不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
        successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面
                    则在登录自动跳转到那个需要登录的页面。不跳转到此。
        unauthorizedUrl :没有权限默认跳转的页面
         */
        factoryBean.setLoginUrl("/toLogin");
        factoryBean.setSuccessUrl("/test");
//        此处因为类型原因,所以自定义了一个类来处理未授权跳转的页面 MyException
//        factoryBean.setUnauthorizedUrl("/noAuth");

        factoryBean.setFilterChainDefinitionMap(filterMap);
        return factoryBean;
    }

    /**
     * 创建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm(){
        return new UserRealm();
    }

    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     *  开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启aop注解支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

UserRealm类

public class UserRealm extends AuthorizingRealm {
    /**
     * 用户
     */
    @Autowired
    private UserService userService;
    /**
     * 角色
     */
    @Autowired
    private RolesService rolesService;
    /**
     * 权限
     */
    @Autowired
    private PermissionsService permissionsService;


    /**
     * 权限验证
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 能进入到这里,表示账号已经通过验证了
        String username = (String) principals.getPrimaryPrincipal();
        User user = userService.selByNameUser(username);
        //获取用户所有角色信息
        Set<String> roleString = rolesService.selByUserIdString(user.getId());
        //查询该用户所拥有的角色
        List<Roles> roleList = rolesService.selByUserIdRoles(user.getId());
        //查询所有角色所拥有的权限, 并保存
        Set<String> perms = new HashSet<>();
        for(Roles role : roleList) {
            Set<String> permString = permissionsService.selByRoleIdPermission(role.getId());
            perms.addAll(permString);
        }

        // 授权对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 把通过数据库获取到的权限保存到shiro
        info.setStringPermissions(perms);
        //把通过数据库获取到的角色保存到shiro
        info.setRoles(roleString);
        return info;
    }

    /**
     * 身份认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取登陆的用户名和密码
        UsernamePasswordToken t = (UsernamePasswordToken) token;
        String username = token.getPrincipal().toString();
        String password = new String(t.getPassword());
        //从数据库中获取登陆的用户
        User user = userService.selByNameUser(username);
        //没找到用户
        if(null==user) {
            throw  new UnknownAccountException();
        }
        //把用户输入的密码进行md5和盐加密一起运算1次
        String encodedPassword = new SimpleHash("md5", password, user.getUsername(), 1).toString();
        //上一步加密后的密文和数据库的密文进行判断
        if (!user.getPassword().equals(encodedPassword)) {
            //密码出错
            throw new IncorrectCredentialsException();
        }
        // 认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
        SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(username, password, getName());
        //清缓存中的授权信息,保证每次登陆 都可以重新授权。因为AuthorizingRealm会先检查缓存有没有 授权信息,再调用授权方法
        super.clearCachedAuthorizationInfo(a.getPrincipals());
        return a;
    }
}

MyException类 在shiroConfig类中已经说明了其作用

@ControllerAdvice
public class MyException {

	/**
	 * 未授权跳转
	 * @return
	 */
	@ExceptionHandler(AuthorizationException.class)
	public String error() {
		return "noAuth";
	}
//
//	/**
//	 * 未认证跳转
//	 * @return
//	 */
//	@ExceptionHandler(UnauthorizedException.class)
//	public String noLogin(){
//		return "login";
//	}
}

common下的util包

SetStringUtil类 将查询到的数据以set集合保存

public class SetStringUtil {
    /**
     * 查询用户所拥有的角色并转换为字符串,用作shiro中的角色权限
     * @param list
     * @return
     */
    public static Set<String> listRoleConversion(List<Roles> list){
        Set<String> set = new HashSet<>();
        for(Roles role : list) {
            set.add(role.getRoleName());
        }
        return set;
    }

    /**
     * 查询角色所拥有的权限并转换为字符串,用作shiro中的角色权限
     * @param list
     * @return
     */
    public static Set<String> listPermissionConversion(List<Permissions> list){
        Set<String> set = new HashSet<>();
        for(Permissions permission : list) {
            set.add(permission.getPermissionName());
        }
        return set;
    }
}

entity包(实体类)

用户

@Data
public class User implements Serializable {

    private static final long serialVersionUID = 8974323100336176485L;

    private Integer id;
    private String username;
    private String password;
}

角色

@Data
public class Roles implements Serializable {

    private static final long serialVersionUID = 6341445277560423251L;

    private Integer id;
    private Integer userId;
    private String roleName;
}

权限

@Data
public class Permissions implements Serializable {

    private static final long serialVersionUID = 6564673599295382095L;

    private Integer id;
    private Integer roleId;
    private String permissionName;
}

mapper层

接口就不展示出来了, 我将mapper.xml贴出来

用户

<?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.lijiajun.mapper.UserMapper">

    <!-- 结果映射 -->
    <resultMap id="usersMap" type="com.lijiajun.entity.User">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="username" property="username" jdbcType="VARCHAR" />
        <result column="password" property="password" jdbcType="VARCHAR" />
    </resultMap>

    <select id="selByNameUser" resultMap="usersMap">
        select * from users where username = #{username}
    </select>
</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.lijiajun.mapper.RolesMapper">

    <!-- 结果映射 -->
    <resultMap id="userRolesMap" type="com.lijiajun.entity.Roles">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="user_id" property="userId" jdbcType="INTEGER" />
        <result column="role_name" property="roleName" jdbcType="VARCHAR" />
    </resultMap>

    <select id="selByUserIdRoles" resultMap="userRolesMap">
        select * from user_roles where user_id = #{userId}
    </select>
</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.lijiajun.mapper.PermissionMapper">

    <!-- 结果映射 -->
    <resultMap id="rolesPermissionsMap" type="com.lijiajun.entity.Permissions">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="permission_name" property="permissionName" jdbcType="VARCHAR" />
        <result column="role_id" property="roleId" jdbcType="INTEGER" />
    </resultMap>

    <select id="selByRoleIdPermission" resultMap="rolesPermissionsMap">
       select * from roles_permissions where role_id = #{roleId}
    </select>
</mapper>

service层

同样不贴出接口,只将实现类贴出

用户

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 根据用户名和密码进行数据库数据匹配
     * @param username
     * @return 返回一个相应于的用户
     */
    @Override
    public User selByNameUser(String username) {
        return  userMapper.selByNameUser(username);
    }
}

角色

@Service
public class RolesServiceImpl implements RolesService {

    @Autowired
    private RolesMapper rolesMapper;

    /**
     * 查询用户的角色, 此信息用于查询角色的权限
     *
     * @param id
     * @return
     */
    @Override
    public List<Roles> selByUserIdRoles(Integer id) {
        return rolesMapper.selByUserIdRoles(id);
    }

    /**
     * 查询用户所拥有的角色并转换为字符串,用作shiro中的角色权限
     *
     * @param userId
     * @return
     */
    @Override
    public Set<String> selByUserIdString(Integer userId) {
        List<Roles> roles = rolesMapper.selByUserIdRoles(userId);
        return SetStringUtil.listRoleConversion(roles);
    }
}

权限

@Service
public class PermissionsServiceImpl implements PermissionsService {

    @Autowired
    private PermissionMapper permissionMapper;

    /**
     * 查询角色所拥有的权限并转换为字符串,用作shiro中的角色权限
     * @param roleId
     * @return
     */
    @Override
    public Set<String> selByRoleIdPermission(Integer roleId) {
        List<Permissions> permissions = permissionMapper.selByRoleIdPermission(roleId);
        return SetStringUtil.listPermissionConversion(permissions);
    }
}

controller层

@Controller
public class IndexController {

	/**
	 * 访问用户列表页面
	 * @return
	 */
	@RequestMapping("/userInfo")
	@RequiresPermissions("user:select")//我的用户并没有这个权限,所以访问不了
	public String userInfo() {
		return "userInfo";
	}

	/**
	 * 访问用户增加页面
	 * @return
	 */
	@RequestMapping("/userAdd")
	@RequiresRoles("admins")//我的用户并没有admin这个角色,所以访问不了
	public String userAdd() {
		return "userAdd";
	}

	/**
	 * 访问用户删除页面
	 * @return
	 */
	@RequestMapping("/userDelete")
	@RequiresPermissions("user:delete")
	public String userDelete() {
		return "userDelete";
	}

}
@Controller
public class UserController {


    /**
     * 登陆逻辑
     * @param
     * @return
     */
    @RequestMapping("/login")
    public String login(String username, String password, Model model){
        //1.获取Subject
        Subject subject = SecurityUtils.getSubject();
        //2.封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        //3.执行登录方法
        try {
            //没发生异常代表登录成功   只要发生异常就代表登陆失败
            subject.login(token);
            return "redirect:/test";
        } catch (UnknownAccountException e) {
            //此异常为用户名不存在
            //e.printStackTrace();
            model.addAttribute("msg","用户名不存在");
            return "/login";
        }catch (IncorrectCredentialsException e){
            //此异常为密码错误
            model.addAttribute("msg","密码错误");
            return "/login";
        }
    }

    /**
     * 跳转到登录页面
     * @return
     */
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "/login";
    }

    /**
     * 登出
     * @return
     */
    @RequestMapping("/logouts")
    @ResponseBody
    public String logout(){
        return "退出成功";
    }

    /**
     * 跳转到用户登陆页面
     * @return
     */
    @RequestMapping("/test")
    public String test(){
        return "/test";
    }

}

html页面

登录页

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2 th:text="${msg}"></h2>
	<form action="/login" method="post">
		用户名:<input type="text" name="username" />
		密码:<input type="text" name="password" />
		<button type="submit">登陆</button>
	</form>
</body>
</html>

功能页

<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>
<div>进入用户列表功能: <a href="/userInfo">用户列表</a></div>
<div>进入用户新增功能: <a href="/userAdd">用户新增</a></div>
<div>进入用户删除功能: <a href="/userDelete">用户删除</a></div>
<a href="/logouts">退出</a>
</body>
</html>

未授权提示页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>未授权提示页面</title>
</head>
<body>
你没有权限访问
</body>
</html>

用户列表页

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>用户信息列表</h1>
</body>
</html>

用户新增页

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>添加用户</h1>
</body>
</html>

用户删除页

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>删除用户</h1>
</body>
</html>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值