shiro-springboot实例

怎甘沉沦 提交于 2020-03-02 20:48:19

研究renren开源的项目,看到别人优雅的使用shiro,学到了学到了

环境搭建:

数据表:

一张user表(存储用户与对应的角色)
在这里插入图片描述

一张role表(存储角色与对应的权限)
在这里插入图片描述

sql语句:

/*
Navicat MySQL Data Transfer

Source Server         : aliyun
Source Server Version : 80017
Source Host           : www.luckycurve.cn:3306
Source Database       : shiro

Target Server Type    : MYSQL
Target Server Version : 80017
File Encoding         : 65001

Date: 2020-03-02 15:34:08
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `role` varchar(255) NOT NULL,
  `perem` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('admin', 'admin');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `salt` varchar(255) NOT NULL,
  `role` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'lee', '4bd12acfd2f4585c2431bb7d38a1d06b463d1a7be4509a82003773bb9e9d991b', '5555', 'admin');

项目搭建:

依赖导入:

创建SpringBoot工程,引入web,shiro-spring,mybatis-plus-boot-starter,mysql-connector-java,lombok,spring-boot-starter-test依赖

建立实体类:

User.java

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -98372415834807875L;
    private Integer id;
    private String username;
    private String password;
    private String salt;
    private String role;
}

注解说明(给没有使用过lombok的小伙伴科普下,IDEA要额外安装lombok框架)

@Data:自动给类中的字段添加getter/setter/equal/hashcode的方法

@Builder:创建对象的时候提供了构造模式

@AllArgsConstructor:全参构造器,Buillder注解依赖全参构造器

@NoArgsConstructor:使用@requestBody注解接收对象时需要使用无参构造器来构造对象

Role.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Role implements Serializable {
    private static final long serialVersionUID = -32834224622124426L;
    
    private String role;
    
    private String perem;

}

mapper层(参考Mybatis-Plus,此处没有任何方法添加):

service层:

UserService.java:

@Service
public class UserService extends ServiceImpl<UserMapper, User> {

    public User getUserByUsername(String username){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        return getOne(wrapper);
    }
}

RoleService.java:

@Service
public class RoleService extends ServiceImpl<RoleMapper, Role> {

    public String getPeremByRole(String role){
        QueryWrapper<Role> wrapper = new QueryWrapper<>();
        wrapper.eq("role",role);
        Role role1 = getOne(wrapper);
        return role1.getPerem();
    }
}

Controller层:

HelloController.java:

@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("/")
    public Result result(){
        return Result.error("haha");
    }

    @PostMapping("/login")
    public Result login(String username,String password){
        Subject subject = SecurityUtils.getSubject();

        try {
            subject.login(new UsernamePasswordToken(username,password));
        } catch (AuthenticationException e) {
            return Result.error(e.getMessage());
        }
        return Result.success("登录成功");
    }

    @GetMapping("/islogin")
    public Result isLogin(){
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()){
            return Result.success("已经通过了身份认证");
        } else {
            return Result.error("还没有通过身份认证");
        }
    }

    @GetMapping("/logout")
    public Result logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return Result.success("退出成功");
    }
}

只是对认证进行了检查

LoginController.java

@RestController
public class LoginController {
    @GetMapping("/")
    @RequiresPermissions(value = {"admin","sss"})
    public Result result(){
        return Result.error("授权成功");
    }

}

对授权进行了检查

注解说明

@RequiresPermissions的value是一个字符串数组,只需要有其中一个权限即可

数据传输层封装:

Result.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result implements Serializable {

    private static final long serialVersionUID = 6734102013300314997L;

    private Integer code;

    private String msg;

    private Object data;

    public static Result error(Integer code, String msg, Object data) {
        return Result.builder().code(code).msg(msg).data(data).build();
    }

    public static Result error(Integer code, String msg) {
        return error(code, msg, null);
    }

    public static Result error(String msg){
        return error(500,msg);
    }

    public static Result success(Integer code, String msg, Object data) {
        return Result.builder().code(code).msg(msg).data(data).build();
    }

    public static Result success(Integer code, String msg) {
        return error(code, msg, null);
    }

    public static Result success(String msg){
        return error(200,msg);
    }

}

所有返回值都使用了这种格式

Shiro相关配置【重点】

shiroUtils.java

public class ShiroUtils {

//    加密算法
    public final static String hashAlgorithmName = "SHA-256";
//    加密次数
    public final static Integer hashIterations =20;

    public static String sha256(String password,String salt){
        return new SimpleHash(hashAlgorithmName,password,salt,hashIterations).toString();
    }
}

指定了加密规则和加密次数,并且提供静态方法直接传入密码和盐直接按照上述规则和次数进行加密,返回加密后的结果

MyRealm.java

@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    @Autowired
    RoleService roleService;

    //    授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User) principalCollection.getPrimaryPrincipal();
        String role = user.getRole();
        String perm = roleService.getPeremByRole(role);
        List<String> list = Arrays.asList(perm.split(","));
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(list);
        return info;
    }

    //    认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        User user = userService.getUserByUsername(token.getUsername());
        if (user == null) {
            throw new UnknownAccountException("账号不存在,请重新输入");
        }

        return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
    }

    //    认证匹配规则
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
        hashedCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
        super.setCredentialsMatcher(hashedCredentialsMatcher);
    }


}

特点

直接室友Component注解将MyRealm注册到了IOC容器当中,不用再去在配置类中单独new一个再添加到容器中,至于设置解密规则的问题,直接也提取到了MyRealm里面,重写了setCredentialsMatcher函数

ShiroConfig.java

@Configuration
public class ShiroConfig {


    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        Map<String, String> map = factoryBean.getFilterChainDefinitionMap();
        map.put("/hello/**","anon");
        map.put("/**","authc");
        factoryBean.setFilterChainDefinitionMap(map);
        return factoryBean;
    }



    //    对Shiro Bean的生命周期的控制
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }


    //    开启Shiro的权限注解支持
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

倒数第三个方法是根据renren开源里面加上去的,感觉以后会有用(具体功能代码注解上标注了)

项目环境即搭建完成

测试可根据Controller里面的逻辑用Postman进行测试,已经自行测试了一遍。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!