因为shiro是一个集合了认证授权的框架,因此既可以用来做登陆认证,也可以用来做用户授权,本次已经将shiro结合spring boot 做成了一个可配置的sdk,方便以后其他项目的使用。

使用时只需配置4步即可完成授权认证的相关功能

1、pom.xml 文件引入相关sdk

<dependency>
 <groupId>com.shiro.authorization</groupId>
 <artifactId>shiro-spring-boot-starter</artifactId>
 <version>1.0.1-SNAPSHOT</version>
 </dependency>




2、用户登陆时调用shiro的用户认证api,用例如下
 

@RequestMapping(value = "login", method = RequestMethod.POST)
public Response<String> login(String username, String password){
    String token = new JwtUtil(jwtProperties).sign(username, password);
    JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(token);
    Subject subject = SecurityUtils.getSubject();
    try {
        subject.login(jwtAuthenticationToken);
        return new Response<String>().success(token);
    } catch (Exception e) {
        e.printStackTrace();
        log.error("登录失败,用户名[{}]", username, e);
        return new Response<String>().fail("登录失败", e.getMessage());
    }
}

具体与shiro有交互的代码有四行

//使用jwtUtil生成一个token(这里可以是jwt也可以是其他的)
String token = new JwtUtil(jwtProperties).sign(username, password);

//将生成的token封装为一个JwtAuthenticationToken参数供shiro在认证时使用
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(token);

//调用 SecurityUtils 获取一个subject(可以理解为与shiro交互的api接口类,与我们的service类似)
Subject subject = SecurityUtils.getSubject();

 //调用 subject.login;进行认证

 subject.login(jwtAuthenticationToken)




3、具体的用户认证实现与相关权限实现
 

/**
 * Shiro-权限相关的业务处理
 */
@Slf4j
@Service
public class ShiroServiceImpl extends JwtAuthorizingRealm implements ShiroService {

    @Resource
    private JwtProperties jwtProperties;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        if (log.isDebugEnabled()) {
            log.debug("进入授权 doGetAuthorizationInfo");
            log.debug("the toke is  {}", principals.toString());
            log.debug("realmName = {}", getName());
        }

        // 该用户具有哪些权限,这里需要从数据库查数据,为了测试方便造了条数据
        String username = new JwtUtil(jwtProperties).getUsername(principals.toString());
        //HashSet<String> permissionsSet = permissionService.get(username)

        HashSet<String> permissionsSet = new HashSet();
        permissionsSet.add("edit");
        authorizationInfo.addStringPermissions(permissionsSet);

        // 该用户具有哪些角色,这里需要从数据库查数据,为了测试方便造了条数据
        //HashSet<String> roleSet = roleService.get(username)
        HashSet<String> roleSet = new HashSet();
        roleSet.add("admin");
        authorizationInfo.addRoles(roleSet);

        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) {
        String token = (String) auth.getCredentials();
        // 解密获得username,用于和数据库进行对比
        String username = new JwtUtil(jwtProperties).getUsername(token);

        if (username == null) {
            throw new AuthenticationException("token invalid");
        }

        //这里需要从数据库查出用户,为了测试方便所以造了条数据
        //User userBean = userService.getUser(username);
        User userBean = new User();
        userBean.setUserName("zhangsan");
        userBean.setPassword("123456");
        if (userBean == null) {
            throw new AuthenticationException("User didn't existed!");
        }
        boolean checkTokenResult = false;
        try {
            checkTokenResult = new JwtUtil(jwtProperties).verify(token, username, userBean.getPassword());
        }catch (TokenExpiredException e){
            throw new UnauthenticatedException("token 已失效");
        }catch (Exception e){
            throw e;
        }
        if (! checkTokenResult) {
            throw new AuthenticationException("Username or password error");
        }
        return new SimpleAuthenticationInfo(token, token, "jwtAuthorizingRealm");
    }
}

首先需要定义一个bean 实现 JwtAuthorizingRealm 这个类中的 doGetAuthorizationInfo,doGetAuthenticationInfo 这两个方法

doGetAuthorizationInfo 这个方法是用来加载当前用户所拥有的权限(通常是需要根据当前用户从数据库中查出来)

doGetAuthenticationInfo 这个方法是用来做具体的用户登录认证

最后需要将 定义好的bean加入spring容器,这里使用的是 spring @Service 注解,其他只要能加载到spring中的方式也都可以


4、业务方法中如果有相关接口需要做权限控制,直接使用shiro相关权限控制注解,用例如下
 

@Slf4j
@RestController
@RequestMapping("/")
public class IndexController {
    @Resource
    private JwtProperties jwtProperties;

    @RequestMapping(value = "login", method = RequestMethod.POST)
    public Response<String> login(String username, String password){
        String token = new JwtUtil(jwtProperties).sign(username, password);
        JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(token);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(jwtAuthenticationToken);
            return new Response<String>().success(token);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("登录失败,用户名[{}]", username, e);
            return new Response<String>().fail("登录失败", e.getMessage());
        }
    }

    /**
     * 无需做权限控制的接口
     * */
    @RequestMapping(value = "anon", method = RequestMethod.POST)
    public Response<String> anon(){
        return new Response<String>().success(null);
    }

    /**
     * 需要登录才能访问的接口
     * */
    @RequiresAuthentication
    @RequestMapping(value = "/require", method = RequestMethod.POST)
    public Response<String> require(){
        return new Response<String>().success(null);
    }

    /**
     * 需要相关角色才能访问的接口
     * */
    @RequiresRoles(value = {"user"})
    @RequestMapping(value = "/role", method = RequestMethod.POST)
    public Response<String> role(){
        return new Response<String>().success(null);
    }

    /**
     * 需要相关权限才能访问的接口
     * */
    @RequiresPermissions(value = {"edit"})
    @RequestMapping(value = "/permission", method = RequestMethod.POST)
    public Response<String> permission(){
        return new Response<String>().success(null);
    }
}
@RequiresAuthentication 此注解表示此接口只有登录用户才能访问
@RequiresRoles(value = {"user"}) 此注解表示此接口只有拥有user角色的用户才能访问 value是一个数组说明可以设置多个角色,他们之间默认是且的关系
@RequiresPermissions(value = {"edit"}) 此注解表示只有拥有edit权限的用户才能访问 value是一个数组说明可以设置多个权限,他们之间默认是且的关系

shiro-spring-boot-starter 已提交到码云

shiro-spring-boot-test  已提交到码云