Spring Boot集成Shiro

目录

概述

一、技术栈

二、环境搭建

1、数据库设计

 2、相关坐标

3、配置文件application.yml(resources文件夹下)

4、实体类

5、数据访问层(Mapper映射、dao层)

6、业务层(service层)

 7、控制器层(controller层)

8、Shiro身份认证、授权、鉴权

三、测试结果


概述

Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份 认证、权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。本篇博客将使用Spring Boot框架集成Shiro,同时结合mybatis框架实现用户的认证以及授权功能。

一、技术栈

主框架:springboot

响应层:springMVC

持久层:mybatis

二、环境搭建

1、数据库设计

(1)pe_user:用户表

(2)pe_user_role:用户角色关系表

(3)pe_role:角色表

(4)pe_role_permission:角色权限关系表

(5)pe_permission:权限表

 2、相关坐标
<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>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>

        <!-- mybatis+mp -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>
        
        <!-- SECURITY begin -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <!-- Shiro版本 -->
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>
</dependency>
3、配置文件application.yml(resources文件夹下)
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shiro_db?serverTimezone=GMT
    username: root
    password: 123456

mybatis:
  configuration:
    map-underscore-to-camel-case: true
  type-aliases-type: com.dytx.shiro_demo_05.doamin
4、实体类

User类:

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "pe_user")
public class User {
    @TableId(value = "id")
    private String id;
    @TableField(value = "username")
    private String username;
    @TableField(value = "password")
    private String password;
    @TableField(value = "salt")
    private String salt;

    private Set<Role> roles;//存储用户所拥有的角色(用户与角色 ==> 多对多)
}

Role类:

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "pe_role")
public class Role {
    @TableId(value = "id")
    private String id;
    @TableField(value = "name")
    private String name;
    @TableField(value = "code")
    private String code;
    @TableField(value = "description")
    private String description;

    private Set<Permission> permissions;//存储角色所拥有的权限(角色与权限 ==> 多对多)
}

Permission类:

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "pe_permission")
public class Permission {
    @TableId(value = "id")
    private String id;
    @TableField(value = "name")
    private String name;
    @TableField(value = "code")
    private String code;
    @TableField(value = "description")
    private String description;
}
5、数据访问层(Mapper映射、dao层)

UserMapper接口:

@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    //按照姓名查询
    @Select("select * from pe_user where username = #{username}")
    User findUserByName(String name);

    //级联查询(按照id查询)
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "username", property = "username"),
            @Result(column = "password", property = "password"),
            @Result(column = "salt", property = "salt"),
            @Result(column = "id", property = "roles",
                    many = @Many(select = "com.dytx.shiro_demo_05.dao.RoleMapper.findRoleById"))
    })
    @Select("select * from pe_user where id = #{id}")
    User findUserById(String id);
}

RoleMapper接口:

@Mapper
public interface RoleMapper extends BaseMapper<Role> {

    //级联查询
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "name", property = "name"),
            @Result(column = "code", property = "code"),
            @Result(column = "description", property = "description"),
            @Result(column = "id", property = "permissions",
                    many = @Many(select = "com.dytx.shiro_demo_05.dao.PermissionMapper.findPermissionById"))
    })
    @Select("select * from pe_role where id in (select role_id from pe_user_role where user_id = #{id})")
    Set<Role> findRoleById(String id);
}

PermissionMapper接口:

@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {
    
    //级联查询
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "name", property = "name"),
            @Result(column = "code", property = "code"),
            @Result(column = "description", property = "description")
    })
    @Select("select * from pe_permission where id in (select permission_id from pe_role_permission where role_id = #{id})")
    Set<Permission> findPermissionById(String id);
}
6、业务层(service层)

UserService类:

@Service
public class UserService {

    //自动注入
    @Autowired(required = false)
    private UserMapper userMapper;

    //按照姓名查询
    public User findUserByName(String name){
        return userMapper.findUserByName(name);
    }

    //按照id查询
    public User findUserDetailById(String id){
        return  userMapper.findUserById(id);
    }
}
 7、控制器层(controller层)

UserController类:

@RequiresPermissions()  --> 访问此方法必须具备的权限

@RestController
public class UserController {

    @Autowired
    private UserService service;

    //个人主页
    @RequestMapping(value = "/user/home")
    public String home() {
        return "访问个人主页成功";
    }

    //添加
    @RequiresPermissions("user-add")
    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String add() {
        return "添加用户成功";
    }

    //查询
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String find() {
        return "查询用户成功";
    }

    //更新
    @RequestMapping(value = "/user/{id}",method = RequestMethod.PUT)
    public String update(@PathVariable String id) {
        return "更新用户成功";
    }

    //删除
    @RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
    public String delete(@PathVariable String id) {
        return "删除用户成功";
    }

    //用户登录
    @RequestMapping(value = "/login")
    public String login(User user){
        try {
            //1.构造登录令牌
            UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
            //2.获取subject
            Subject subject = SecurityUtils.getSubject();
            //3.调用subject进行登录
            subject.login(token);
            return "登陆成功!";
        } catch (AuthenticationException e) {
            return "用户名或密码错误!";
        }
    }

    //未登陆与未授权页面
    @RequestMapping(value = "/autherror")
    public String autherror(){
        return "未登录!";
    }
}
8、Shiro身份认证、授权、鉴权

shiro.ini配置文件:(resources文件夹下)

[main]
definitionRealm=com.dytx.shiro_demo_05.realm.MyRealm
securityManager.realms=$definitionRealm

ShiroConfiguration配置类:

@Configuration
public class ShiroConfiguration {
    /**
     * 1.创建shiro自带cookie对象
     */
    @Bean
    public SimpleCookie sessionIdCookie(){
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName("ShiroSession");
        return simpleCookie;
    }

    //2.创建realm
    @Bean
    public MyRealm getRealm(){
        return new MyRealm();
    }

    /**
     * 3.创建会话管理器
     */
    @Bean
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(sessionIdCookie());
        sessionManager.setGlobalSessionTimeout(3600000);
        return sessionManager;
    }

    //4.创建安全管理器
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(getRealm());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    /**
     * 5.保证实现了Shiro内部lifecycle函数的bean执行
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 6.开启对shiro注解的支持
     *   AOP式方法级权限检查
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /**
     * 7.配合DefaultAdvisorAutoProxyCreator事项注解权限校验
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
        return authorizationAttributeSourceAdvisor;
    }

    //8.配置shiro的过滤器工厂再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
    @Bean
    public ShiroFilterFactoryBean shiroFilter(){
        //1.创建过滤器工厂
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2.设置安全管理器
        filterFactory.setSecurityManager(defaultWebSecurityManager());
        //3.通用配置(跳转登录页面,为授权跳转的页面)
        filterFactory.setLoginUrl("/autherror");//跳转url地址
        //4.设置过滤器集合
        //key = 拦截的url地址
        //value = 过滤器类型
        Map<String,String> filterMap = new LinkedHashMap<>();
        //key:请求规则   value:过滤器名称
        filterMap.put("/login","anon");//当前请求地址可以匿名访问
        filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问
        //在过滤器工程内设置系统过滤器
        filterFactory.setFilterChainDefinitionMap(filterMap);
        return filterFactory;
    }
}

DigestsUtil类:获取加密后的密码和盐值存入数据库,在认证时,Shiro会自动按照加密算法实现对明文的加密,然后与数据库进行匹配,成功则登录认证成功,否则失败。

public class DigestsUtil {

    //加密算法名称
    public static final String SHA1 = "SHA-1";

    //加密次数
    public static final Integer COUNTS =369;

    /**
     * @Description sha1方法
     * @param input 需要散列字符串
     * @param salt 盐字符串
     * @return
     */
    public static String show(String input, String salt) {
        return new SimpleHash(SHA1, input, salt,COUNTS).toString();
    }

    /**
     * @Description 随机获得salt字符串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }

    /**
     * @Description 生成密码字符密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
        Map<String,String> map = new HashMap<>();
        String salt = generateSalt();
        String password =show(passwordPlain,salt);
        map.put("salt", salt);
        map.put("password", password);
        return map;
    }

    //将控制台输出的密文和盐值以及张三新增进数据库中的pe_user表中
    public static void main(String[] args) {
//        String name = "张三";
//        String pwd = "123456";
//        Map map = entryptPassword(pwd);
//        System.out.println(map.toString());

        String name = "张三";
        String pwd = "123456";
        Map map = entryptPassword(pwd);
        System.out.println(map.toString());
    }
}

MyRealm类:

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService service;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1.获取已认证的用户数据
        String id = (String) principalCollection.getPrimaryPrincipal();//得到唯一的安全数据
        //2.根据用户数据获取用户的权限信息(所有角色,所有权限)
        User user = service.findUserDetailById(id);
        Set<String> roles = new HashSet<>();//所有角色
        Set<String> perms = new HashSet<>();//所有权限
        for (Role role : user.getRoles()) {
            roles.add(role.getId());
            for (Permission permission : role.getPermissions()) {
                perms.add(permission.getCode());
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(perms);
        info.setRoles(roles);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.获取登录的用户名密码(token)
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();//用户录入的账号
        //2.根据用户名查询数据库
        //mybatis情景下:user对象中包含ID,name,pwd(匿名)
        //JPA情景下:user对象中包含ID,name,pwd(匿名),set<角色>,set<权限>
        User user = service.findUserByName(username);
        //3.判断用户是否存在或者密码是否一致
        if (user != null) {
            //4.如果一致返回安全数据
            //构造方法:安全数据,密码(匿名),混淆字符串(salt),realm域名
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getId(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), "myRealm");
            return info;
        }
        //5.不一致,返回null(抛出异常)
        return null;
    }

    @PostConstruct
    public void initCredentialsMatcher() {
        //指定密码算法
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
        //指定迭代次数
        hashedCredentialsMatcher.setHashIterations(DigestsUtil.COUNTS);
        //生效密码比较器
        setCredentialsMatcher(hashedCredentialsMatcher);
    }
}

BaseExceptionHandler类:(自定义异常处理器)

//自定义的公共异常处理器
// 1.声明异常处理器
// 2.对异常统一处理
@ControllerAdvice
public class BaseExceptionHandler {
    @ExceptionHandler(value = AuthorizationException.class)
    @ResponseBody
    public String error(HttpServletRequest request, HttpServletResponse response,AuthorizationException e){
        return "未授权-异常处理器实现";
    }
}

三、测试结果

利用DigestsUtil类获取用户张三和李四的密文密码、盐值插入到数据库pe_user表中,张三的id为1,李四的id为2。有数据库表关系可知,张三拥有的角色有系统管理员和普通员工,拥有的权限有添加用户、查询用户、更新用户和删除用户;李四拥有的角色有普通员工,拥有的权限仅有用户主页。

1、张三

用户登录:

 权限查询(以新增为例):

 2、李四

用户登录:

 权限查询:(李四没有用户新增的权限,由自定义异常处理器处理异常)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值