【shiro】SpringBoot整合shiro实例

项目结构

粗暴一点,上图

展开是这样的

由于我把用户角色和密码放在了一张用户表上,这里Role类以及Mapper并没有使用

另外为了只是简单的测试,没有加上权限操作字段,使用表如下


导入依赖

<dependencies>
    //controller层需要
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    //数据库连接
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.13</version>
    </dependency>
    //mybatis整合
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.1</version>
    </dependency>
    //测试
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    //shiro整合
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
    //lombok使用
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.4</version>
    </dependency>
</dependencies>

配置文件

log4j.properties

# Configure logging for testing: optionally with log file
log4j.rootLogger=info, stdout  
# log4j.rootLogger=WARN, stdout, logfile

log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n  

log4j.appender.logfile=org.apache.log4j.FileAppender  
log4j.appender.logfile.File=target/spring.log  
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout  
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

application.properties

server.port=8080  

spring.datasource.url=jdbc:mysql://localhost:3306/shirodemo?useUnicode=true&useSSL=false&characterEncoding=utf8&&serverTimezone=GMT&allowPublicKeyRetrieval=true  
spring.datasource.username=root  
spring.datasource.password=olonn  
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver  

mybatis.mapper-locations=classpath:mapper/*.xml

UserMapper.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.example.demo.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <!--    <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>-->

    <select id="selectPasswordByName" resultType="java.lang.String">
        SELECT password FROM USER WHERE username = #{name}
    </select>

    <select id="selectRoleByName" resultType="java.lang.String">
        SELECT role FROM USER WHERE username = #{name}
    </select>
</mapper>

bo类

User

@Data
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
    private String role;
}

shiro配置

ShiroConfig
这是shiro配置的核心,我们要把自定义realm、加密方式等注入securityManager对象,然后把该对象放入shiroFilterFactoryBean,并配置请求的url需要用户登陆或者有无权限,权限信息为

@Configuration
public class ShiroConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(ShiroConfig.class);

    @Bean
    public UserRealm getRealm(){
        return new UserRealm();
    }

    @Bean(name = "securityManager")
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(getRealm());
        return securityManager;
    }

    //这里可以进行加密方式配置
    @Bean
    public HashedCredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(3);
        return hashedCredentialsMatcher;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
        shiroFilterFactoryBean.setLoginUrl("/notLogin");
        //无权限时的跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/noRole");

        //设置拦截器
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        //游客,开发权限
        filterChainDefinitionMap.put("/guest/**","anon");
        //用户
        filterChainDefinitionMap.put("/user/**","roles[user]");
        //管理员
        filterChainDefinitionMap.put("/admin/**","roles[admin]");
        //登陆接口
        filterChainDefinitionMap.put("/login/**","anon");
        //拦截所有其他接口,必须放在所有权限验证的最后,不然会把所有url全部拦截
        filterChainDefinitionMap.put("/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        LOGGER.info("shiro拦截工厂类启动成功");
        return shiroFilterFactoryBean;
    }
}

Mapper接口(一般要用service操作)

@Mapper
public interface UserMapper {
    /**
     * fetch data by rule id
     *
     * @param username
     * @return Result<String>
     */
    String selectPasswordByName(@Param("name") String username);

    /**
     * fetch data by rule id
     *
     * @param username
     * @return Result<String>
     */
    String selectRoleByName(@Param("name") String username);
}

自定义Realm

@Component
public class UserRealm extends AuthorizingRealm {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserRealm.class);

    @Autowired
    private UserMapper userMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        LOGGER.info("-----权限验证-----");
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole(userMapper.selectRoleByName(username));
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        LOGGER.info("-----身份验证-----");
        String username = (String) token.getPrincipal();
        String password = userMapper.selectPasswordByName(username);
        if (null==password){
            throw new AccountException("用户名不正确");
        }
        return new SimpleAuthenticationInfo(username,password,getName());
    }

    private ByteSource generateSalt(String salt){
        return ByteSource.Util.bytes(salt);
    }
}

为了简单示例,没有加加密操作,另外登陆失败的异常没有处理

控制层

LoginController

@RestController
public class LoginController {

    private static HashMap<String,String> myp = new HashMap<>(16);

    @RequestMapping("/notLogin")
    @ResponseBody
    public HashMap<String, String> notlogin(){
        myp.put("hello","world");
        return myp;
    }

    @RequestMapping("/login/{username}/{password}")
    @ResponseBody
    public HashMap<String, String> login(@PathVariable("username")String username,@PathVariable("password")String password){
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        subject.login(token);

        myp.put("for","login");
        return myp;
    }

    @RequestMapping("/noRole")
    @ResponseBody
    public HashMap<String, String> noRole(){
        myp.put("for","login");
        return myp;
    }
}

UserController

@RestController
public class UserController {

    private static HashMap<String ,String > mymap = new HashMap<>(16);

    @RequestMapping("/admin/{admin}")
    @ResponseBody
    public HashMap admin(@PathVariable("admin")String admin){
        mymap.put("admin",admin);
        return mymap;
    }

    @RequestMapping("/user/{user}")
    @ResponseBody
    public HashMap user(@PathVariable("user")String user){
        mymap.put("user",user);
        return mymap;
    }

    @RequestMapping("/guest/{guest}")
    @ResponseBody
    public HashMap guest(@PathVariable("guest")String guest){
        mymap.put("guest",guest);
        return mymap;
    }
}

测试

上面代码算是全部写完了,接下来验证

运行springboot启动类

访问http://localhost:8080/xxxxxx
自动跳转到/notLogin

访问http://localhost:8080/guest/xxxx

访问http://localhost:8080/user/zzzz同样跳转到notLogin

访问http://localhost:8080/login/ccc/123

找不到用户,后台抛出异常未处理

访问http://localhost:8080/login/bbb/123

登陆成功

然后访问http://localhost:8080/user/我登陆了

可以成功访问

访问http://localhost:8080/admin/mmmm
自动跳转到/noRole