因为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 已提交到码云