本文结合一个简单的权限模块设计来实现Shiro的集成。
新建实体如下:
权限实体Permission:id,code,name,parent_id;
角色实体Role:id,code,name;
用户实体User:id,username,password,role(简化设计,一个用户只能有一个角色,因此User表中设置一个role_id字段关联角色);
Role和Permission的关系通过role_permission关系表维护。
具体见源代码/wu-boy/,parker-shiro-base模块,resources目录下有建表和初始化SQL。
SpringBoot集成Shiro引入shiro-spring-boot-web-starter即可。
首先自定义MyRealm,在这个Realm中做登录认证和用户授权。
package ;
import ;
import ;
import ;
import .*;
import ;
import ;
import ;
import ;
import ;
import org.;
import org.;
import ;
import ;
/**
* @author: wusq
* @date: 2018/12/8
*/
public class MyRealm extends AuthorizingRealm {
private static final Logger log = ();
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
("授权");
String username = (String) ();
SimpleAuthorizationInfo authorizationInfo = null;
try {
authorizationInfo = new SimpleAuthorizationInfo();
User user = (username);
(().getCode());
List<Permission> list = ().getPermissionList();
for(Permission p:list){
(());
}
} catch (Exception e) {
("授权错误{}", ());
();
}
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
("登录认证");
String username = (String) ();
User user = (username);
if(user == null) {
throw new UnknownAccountException(); // 没找到帐号
}
/*if((())) {
throw new LockedAccountException(); //帐号锁定
}*/
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以在此判断或自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
(), //用户名
(), //密码
getName() //realm name
);
return authenticationInfo;
}
}
ShiroConfig配置如下
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author: wusq
* @date: 2018/12/8
*/
@Configuration
public class ShiroConfig {
/**
* 注入自定义的realm,告诉shiro如何获取用户信息来做登录认证和授权
*/
@Bean
public Realm realm() {
return new MyRealm();
}
/**
* 这里统一做鉴权,即判断哪些请求路径需要用户登录,哪些请求路径不需要用户登录。
* 这里只做鉴权,不做权限控制,因为权限用注解来做。
* @return
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
// 设置哪些请求可以匿名访问
("/login/**", "anon");
// 由于使用Swagger调试,因此设置所有Swagger相关的请求可以匿名访问
("/", "anon");
("/swagger-resources", "anon");
("/swagger-resources/configuration/security", "anon");
("/swagger-resources/configuration/ui", "anon");
("/v2/api-docs", "anon");
("/webjars/springfox-swagger-ui/**", "anon");
//除了以上的请求外,其它请求都需要登录
("/**", "authc");
return chain;
}
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
/**
* setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
* 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。
* 加入这项配置能解决这个bug
*/
(true);
return creator;
}
}
新建PermissionController用来测试
package ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author: wusq
* @date: 2018/12/8
*/
@Api(description = "资源服务")
@RestController
@RequestMapping("/security/permissions/")
public class PermissionController {
@ApiOperation("查询资源")
@GetMapping()
@RequiresPermissions("permission:retrieve")
public String get(){
return "有permission:retrieve这个权限的用户才能访问,不然访问不了";
}
}
新建LoginController完成登录功能
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import org.;
import org.;
import ;
import ;
import ;
import ;
import ;
/**
* @author: wusq
* @date: 2018/12/8
*/
@Api(description = "登录服务")
@RestController
@RequestMapping("/login/")
public class LoginController {
private static final Logger log = ();
@Autowired
private UserService userService;
@ApiOperation("登录")
@GetMapping("{username}/{password}")
public User login(@PathVariable String username, @PathVariable String password){
User result = null;
Subject subject = ();
// 此处的密码应该是按照后台的加密规则加密过的,不应该传输明文密码
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
(token);
result = (username);
} catch (UnknownAccountException e) {
("用户名或密码错误");
();
} catch (IncorrectCredentialsException e) {
("用户名或密码错误");
();
} catch (AuthenticationException e) {
//其他错误,比如锁定,如果想单独处理请单独catch处理
("其他错误");
();
}
return result;
}
}
启动工程后,可以先访问PermissionController中的路径,会提示404,因为没有登录,被Shiro拦截了。
再测试登录功能,通过正确的用户名和密码登录后,再访问PermissionController会返回正常结果。
相关的注意事项都在代码注释中说明了。
附上加密工具类EncryptUtils,方便对Shiro的加密方式进行理解和测试
package ;
import ;
import ;
import ;
import ;
import ;
/**
* 加解密工具类
* @author: wusq
* @date: 2018/12/8
*/
public class EncryptUtils {
/**
* 默认加密次数
*/
public static final Integer DEFAULT_ITERATIONS = 1;
/**
* Shiro的MD5加密,加密方式是对字符串salt+password进行加密
* @param salt 盐
* @param password 密码
* @return
*/
public static String shiroMd5(String salt, String password){
String algorithmName = "MD5";
ByteSource byteSalt = (salt);
SimpleHash simpleHash = new SimpleHash(algorithmName, password, byteSalt, DEFAULT_ITERATIONS);
return ();
}
/**
* Java的MD5加密,加密方式是对字符串salt+password进行加密
* @param salt 盐
* @param password
* @return
*/
public static String md5(String salt, String password){
String result = null;
byte[] bytes = null;
try {
// 生成一个MD5加密计算摘要
MessageDigest md = ("MD5");
// 对字符串进行加密
((salt + password).getBytes());
// 获得加密后的数据
bytes = ();
// 将加密后的数据转换为16进制数字
result = new BigInteger(1, bytes).toString(16);// 16进制数字
// 如果生成数字未满32位,需要前面补0
for (int i = 0; i < 32 - (); i++) {
result = "0" + result;
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有md5这个算法!");
}
return result;
}
public static void main(String[] args) {
String password1 = shiroMd5("admin", "12345678");
(password1);
String password2 = md5("admin", "12345678");
(password2);
// 两者加密结果相同
((password2));
}
}
源代码
/wu-boy/
parker-shiro-base模块
EncryptUtils位于parker-common模块
参考资料
1、跟我学Shiro
2、Shiro用starter方式优雅整合到SpringBoot中
3、springboot(十四):springboot整合shiro-登录认证和权限管理
4、Shiro登陆异常 did not match the expected credentials. 是为什么