SpringBoot整合Shiro(第一篇)

主要实现用户的认证,授权,鉴权三个功能

微服务架构:开发工具IDEA

采用eureka作为注册中心,SpringBoot/Cloud+shiro+前端模板(thymeleaf)+mysql5.7

eureka注册中心服务请参照eureka创建全过程(idea)

整合步骤如下:新建一个module工程

  • pom文件
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--SpringBoot集成shiro导的依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>1.5.21.RELEASE</version>
        </dependency>
  •  shiro目录结构如图

注意:这里你只需要两个服务就行了  一个是eureka-server(注册中心),另一个shiro-server(权限服务)

shio-server的启动类上别忘了加一个注解:@EnableEurekaClient(该服务注册到eureka上)

  • application.yml配置如下
#shiro服务端口
server:
  port: 7777
#shiro服务名称  
spring:
  application:
    name: shiro-server
#关联更新数据库,自动建表update:有变动的字段就更新,不存在表就建表    
  jpa:
    hibernate:
      ddl-auto: update
#jdbc4个基本配置 驱动/url/密码/用户名      
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/microservices?characterEncoding=utf-8&useSSL=true
    password: root
    username: root
#关闭缓存
  thymeleaf:
    cache: false
#静态html文件存储位置,默认templates目录下
    prefix: classpath:/templates/
#默认后缀为html,之后的html转发无需加后缀html    
    suffix: .html
#该服务IP地址    
eureka:
  instance:
    hostname: localhost
#注册到注册中心的地址    
  client:
    service-url:
      defaultZone:  http://localhost:8761/eureka/
#控制台打印日志信息      
logging:
  level:
    root: INFO
    org.hibernate: INFO
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
    com.springms: DEBUG
  • templates目录下html文件代码
/**
**登录页面
**/
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
    <font color="red" th:text="${message}"></font>
    <form action="/loginFrom" method="post">
        用户名<input type="text" name="username" /><br/>
        密码<input type="password" name="password"/><br/>
        <input type="submit" value="登录"/>
    </form>
</body>
</html>
/**
**登录成功页面
**/
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>登录成功</title>
</head>
<body>
<a href="/find">查看数据页面</a><br/>
<a href="/delete">删除数据页面</a>
</body>
</html>
/**
**数据页面
**/
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
<h1>查看数据页面</h1>
</body>
</html>
/**
**如果该用户没有权限,跳转页面
**/
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
<h1>未授权页面展示</h1>
</body>
</html>
/**
**删除页面
**/
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>删除页面</title>
</head>
<body>
<h1>删除页面展示</h1>
</body>
</html>
  •  shiro服务启动类代码​​​​​​​
@SpringBootApplication
@EnableEurekaClient
public class ShiroServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShiroServerApplication.class, args);
    }

}
  • mysql数据库3个表创建代码
/**
**用户表users
**/
CREATE TABLE `users`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `salt` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '盐值',
  `role_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色列表',
  `locked` tinyint(1) NULL DEFAULT 0 COMMENT '是否锁定',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `idx_sys_users_username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- 为用户表插入一条数据
-- ----------------------------
INSERT INTO `users` VALUES (1, 'mark', '6d295738eb6579053ac46a9ca1902583', NULL, NULL, 0);
/**
**角色表user_roles
**/
CREATE TABLE `user_roles`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色编号',
  `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  `role_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- 为角色表插入一条数据
-- ----------------------------
INSERT INTO `user_roles` VALUES (1, 'mark', 'admin');
/**
**权限表roles_permissions
**/
CREATE TABLE `roles_permissions`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `role_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限编号',
  `permission` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
--为权限表插入一条数据
-- ----------------------------
INSERT INTO `roles_permissions` VALUES (1, 'admin', 'user:select');
INSERT INTO `roles_permissions` VALUES (2, 'admin', 'user:add');

  • Controller接收前端请求,并负责认证,鉴权~​​​​​​​
@Controller
public class LoginController {
    /**
     * shiro认证登录
     *
     * @param username
     * @param password
     * @return
     */
    @PostMapping(value = "/loginFrom")
    public String login(String username, String password,Model model) {
        try {
            //shiro Token处理类
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            //获得提交认证类
            Subject subject = SecurityUtils.getSubject();
            subject.login(token);
        } catch (UnknownAccountException e){
            model.addAttribute("message",e.getMessage());
            return "login";
        } catch (IncorrectCredentialsException e) {
            model.addAttribute("message","用户名或密码错误!");
            return "login";
        } catch (AuthenticationException e) {
            model.addAttribute("message",e.getMessage());
            return "login";
        }
        return "success";
    }

    /**
     * 跳转登录页
     *
     * @return
     */
    @RequestMapping(value = "/login")
    public String goToLogin() {
        return "login";
    }

    /**
     * 查看数据页面
     * @return
     */
    @RequestMapping(value = "/find")
    public String goToFind() {
        return "find";
    }
    /**
     * 查看数据页面
     * @return
     */
    @RequestMapping(value = "/delete")
    public String goToDelete() {
        return "delete";
    }
    /**
     * 跳转成功页面
     * @return
     */
    @RequestMapping(value = "/success")
    public String goTosuccess() {
        return "success";
    }

    /**
     * 未授权(也就是没有权限的页面)
     * @return
     */
    @RequestMapping(value = "/noauth")
    public String goTonoauth() {
        return "noauth";
    }
}
  • SpringBoot核心拦截器配置​​​​​​​
@Configuration
public class ShiroConfig {
    /**
     * 配置shiro web过滤器, 拦截/转发请求
     */
    @Bean
    public ShiroFilterFactoryBean shiroWebConfigFilter() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //配置拦截链 LinkedHashMap有序,shiro会根据添加的顺序进行拦截
        // Map<K,V> K指的是拦截的url V值的是该url是否拦截
        //添加securityManager环境
        //将shiro环境添加过滤器链~
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        Map<String, String> filterChainMap = new LinkedHashMap<>();

        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
        filterChainMap.put("/loginFrom", "anon");
        filterChainMap.put("/login", "anon");
        filterChainMap.put("/success", "anon");
        /**
         * 添加授权过滤器 验证当前用户是否有删除权限,如果没有跳转未授权页面或抛出异常信息
         */
        filterChainMap.put("/delete", "perms[user:delete]");
        filterChainMap.put("/**", "authc");
        //拦截所有请求直接到登录页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
        //对某些资源进行拦截处理
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 构建secrityManager环境
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = null;
        securityManager = new DefaultWebSecurityManager();
        //将自定义realm注入环境中去
        CustomRealm customRealm = new CustomRealm();
        securityManager.setRealm(customRealm);
        //shiro加密~在前端传入密码进后端后对密码进行加密
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        customRealm.setCredentialsMatcher(matcher);
        return securityManager;
    }
}
  • 自定义Realm
/**
 * 自定义realm
 */
@SuppressWarnings("all")
@Component
public class CustomRealm extends AuthorizingRealm {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DataSource dataSource;
    public static CustomRealm customRealm;

    /**
     * 初始化普通类之前 将数据源和JDBC模板赋值给当前Bean
     * 之后将该Bean添加SpringBoot管理
     * SpringBoot好像普通类在运行时自动注入都会找不到,原因应该是Springboot的某种特性吧
     */
    @PostConstruct
    public void  inits(){
        customRealm=this;
        customRealm.dataSource=this.dataSource;
        customRealm.jdbcTemplate=this.jdbcTemplate;
    }

    //校验该用户有哪些权限
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String userName = (String) principalCollection.getPrimaryPrincipal();
        //获取角色数据
        Set<String> users = getRolesByUserName(userName);
        Set<String> permisss = getPermissionByUserName(userName);
        SimpleAuthorizationInfo so = new SimpleAuthorizationInfo();
        so.setStringPermissions(permisss);
        so.setRoles(users);
        return so;
    }

    //该角色有什么权限
    private Set<String> getPermissionByUserName(String userName) {
        Set<String> rolesName = new HashSet<String>();
        ResultSet resultSet = null;
        String roleSql = "SELECT permission FROM\n" +
                "roles_permissions WHERE role_name = (select role_name from user_roles where username =" + "'" + userName + "')";
        try {
            customRealm.jdbcTemplate.setDataSource(customRealm.dataSource);
            List<String> permission = customRealm.jdbcTemplate.query(roleSql, new RowMapper<String>() {
                @Override
                public String mapRow(ResultSet resultSet, int i) throws SQLException {
                    return resultSet.getString("permission");
                }
            });
            rolesName.addAll(permission);

            return rolesName;
        } catch (Exception e) {
            throw new AuthenticationException("获得角色权限失败");
        }
    }

    //用户属于什么角色
    private Set<String> getRolesByUserName(String userName) {
        Set<String> rolesName = new HashSet<String>();
        String roleSql = "select role_name from user_roles where username = " + "'" + userName + "'";
        try {
            customRealm.jdbcTemplate.setDataSource(customRealm.dataSource);
            List<String> role_name = customRealm.jdbcTemplate.query(roleSql, new RowMapper<String>() {
                @Override
                public String mapRow(ResultSet resultSet, int i) throws SQLException {
                    return resultSet.getString("role_name");
                }
            });
            rolesName.addAll(role_name);
        } catch (Exception e) {
            throw new AuthenticationException("获得用户的角色失败");
        }
        return rolesName;

    }

    //校验用户名密码是否正确
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获得用户输入账号
        String username = (String) authenticationToken.getPrincipal();
        //数据库校验
        String password = getPasswordByUserName(username);
        //如果密码为空 则用户不存在
        //如果不为空 SimpleAuthenticationInfo做身份验证处理
        //加盐加密
        //如果用户名不存在shiro底层会抛出UnknownAccountException
        if (StringUtils.isEmpty(password)) {
            throw new UnknownAccountException("用户名不存在");
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, "CustomRealm");
        simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
        return simpleAuthenticationInfo;
    }

    //根据用户名获得密码
    @SuppressWarnings("all")
    private String getPasswordByUserName(String username) {
        String sql = "select password from users where  username = " + "'" + username + "'";
        String password = null;
        try {
            customRealm.jdbcTemplate.setDataSource(customRealm.dataSource);
            password = customRealm.jdbcTemplate.queryForObject(sql, String.class);
            //jdbcTemplate 在查询字符串是null时会抛出异常
        } catch (EmptyResultDataAccessException e) {
            throw  new AuthenticationException("用户名不存在");
        }
        return password;
    }
}

自此搭建完成里面核心代码我都有注释,建议细致的了解shiro的执行过程,如果有疑问或者问题加我Q或V

QQ:2509647976                        微信:x331191249

  •  演示效果图

​​​​​​​该实例只解决了简单的认证,鉴权,后续会结合jwt+oauth2进行权限控制,继续努力。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值