6.shiro学习笔记(5) – spring boot 整合Shiro
在pom 中添加:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
准备SQL:
drop table if exists sys_users;
drop table if exists sys_roles;
drop table if exists sys_permissions;
drop table if exists sys_users_roles;
drop table if exists sys_roles_permissions;
create table sys_users (
id bigint auto_increment,
username varchar(100),
password varchar(100),
salt varchar(100),
locked bool default false,
constraint pk_sys_users primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_sys_users_username on sys_users(username);
create table sys_roles (
id bigint auto_increment,
role varchar(100),
description varchar(100),
available bool default false,
constraint pk_sys_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_sys_roles_role on sys_roles(role);
create table sys_permissions (
id bigint auto_increment,
permission varchar(100),
description varchar(100),
available bool default false,
constraint pk_sys_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_sys_permissions_permission on sys_permissions(permission);
create table sys_users_roles (
user_id bigint,
role_id bigint,
constraint pk_sys_users_roles primary key(user_id, role_id)
) charset=utf8 ENGINE=InnoDB;
create table sys_roles_permissions (
role_id bigint,
permission_id bigint,
constraint pk_sys_roles_permissions primary key(role_id, permission_id)
) charset=utf8 ENGINE=InnoDB;
创建Realm:
public class UserRealm extends AuthorizingRealm
{
private static final Logger LOGGER = LoggerFactory.getLogger(UserRealm.class);
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
private SysUsersService usersService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
{
Map<String, Object> roleMap = new HashedMap();
String userName = (String) principalCollection.getPrimaryPrincipal();
SysUsersBean usersBean = usersService.findByUsername(userName);
roleMap.put("userName", usersBean.getUsername());
String roleSql = "SELECT r.role FROM sys_roles as r,sys_users as u, sys_users_roles ur WHERE u.id = ur.user_id and ur.role_id = r.id and u.username = :userName ";
// 获取角色列表
List<String> roleList = jdbcTemplate.queryForList(roleSql, roleMap, String.class);
String pSql = "SELECT p.permission FROM sys_roles as r, sys_permissions as p, sys_roles_permissions as rp \n" +
"WHERE r.id = rp.role_id and p.id = rp.permission_id and r.role in (:roles)";
roleMap.put("roles", roleList);
// 获取权限列表
List<String> pList = jdbcTemplate.queryForList(pSql, roleMap, String.class);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roleList);
info.addStringPermissions(pList);
LOGGER.info("doGetAuthorizationInfo");
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException
{
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userName = (String)token.getPrincipal();
// findByUsername是我自定义的一个方法,根据Username 获取user,所以说注意Username一定是唯一的
SysUsersBean usersBean = usersService.findByUsername(userName);
if(usersBean == null){
return null;
}
LOGGER.info("doGetAuthenticationInfo");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
//用户名
usersBean.getUsername(),
//密码
usersBean.getPassword(),
//salt=username+salt(这是加盐)
ByteSource.Util.bytes(usersBean.credentialsSalt()),
//realm name
getName()
);
return authenticationInfo;
}
}
初始化shiro
@Configuration
public class ShiroConfig
{
/**
* 凭证匹配器
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//md5加密2次
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//加密次数
hashedCredentialsMatcher.setHashIterations(2);
// 开启 盐 ,默认是false
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* 自定义realm
*
* @return
*/
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 安全管理器
* 注:使用shiro-spring-boot-starter 1.4时,返回类型是SecurityManager会报错,直接引用shiro-spring则不报错
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 设置过滤规则
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
//注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
//所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/addUser", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
/**
* setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
* 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。
* 加入这项配置能解决这个bug
*/
creator.setUsePrefix(true);
return creator;
}
}
自定义加密:
public class PasswordHelper {
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
private String algorithmName = "md5";
private final int hashIterations = 2;
public void encryptPassword(User user) {
user.setSalt(randomNumberGenerator.nextBytes().toHex());
String newPassword = new SimpleHash(
algorithmName,
user.getPassword(),
ByteSource.Util.bytes(user.credentialsSalt()),
hashIterations).toHex();
user.setPassword(newPassword);
}
}
实现权限的控制通常有两种:
-
只用注解控制鉴权授权
-
在控制器类上使用shiro提供的种注解来做控制:
注解 功能 @RequiresGuest 只有游客可以访问 @RequiresAuthentication 需要登录才能访问 @RequiresUser 已登录的用户或“记住我”的用户能访问 @RequiresRoles 已登录的用户需具有指定的角色才能访问 @RequiresPermissions 已登录的用户需具有指定的权限才能访问
// 只有admin角色才能访问 @RequiresRoles("admin") @GetMapping("create") public Object create() { return "sss"; }
-
-
用url配置控制鉴权授权
shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限:
配置缩写 对应的过滤器 功能 anon AnonymousFilter 指定url可以匿名访问 authc FormAuthenticationFilter 指定url需要form表单登录,默认会从请求中获取 username
、password
,rememberMe
等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。authcBasic BasicHttpAuthenticationFilter 指定url需要basic登录 logout LogoutFilter 登出过滤器,配置指定url就可以实现退出功能,非常方便 noSessionCreation NoSessionCreationFilter 禁止创建会话 perms PermissionsAuthorizationFilter 需要指定权限才能访问 port PortFilter 需要指定端口才能访问 rest HttpMethodPermissionFilter 将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释 roles RolesAuthorizationFilter 需要指定角色才能访问 ssl SslFilter 需要https请求才能访问 user UserFilter 需要已登录或“记住我”的用户才能访问 在config中注入bean:
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
//注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
//所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/addUser", "anon");
filterChainDefinitionMap.put("/captcha.jpg", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
这样写可能看起来很复杂,你可以将以上二者结合起来。
参考: