Springboot整合shiro
了解了shiro的基本概念后,现在就基于springboot整合shiro
pom
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
</dependencies>
model
/**
* 用户实体类
*/
public class UserDO {
private Long userId;
private String username;
private String password;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
/**
* 资源实体类
*/
public class ResourceDO {
String resId;
String resName;
public String getResId() {
return resId;
}
public void setResId(String resId) {
this.resId = resId;
}
public String getResName() {
return resName;
}
public void setResName(String resName) {
this.resName = resName;
}
}
service层
@Service
public class BaseService {
/**
* 根据用户名查询用户
* @param username
* @return
*/
public UserDO selectByUsername(String username) {
// 模拟DAO查询结果
UserDO user = new UserDO();
user.setUserId(1L);
user.setUsername(username);
user.setPassword("123456");
return user;
}
/**
* 根据用户名查询用户权限列表
* @param username
* @return
*/
public List<ResourceDO> selectResourceByUsername(String username) {
// 模拟DAO查询结果
List<ResourceDO> resourceDOList = new LinkedList<>();
ResourceDO res1 = new ResourceDO();
res1.setResId("CLIENT:ADD");
res1.setResName("客户新增");
resourceDOList.add(res1);
ResourceDO res2 = new ResourceDO();
res2.setResId("CLIENT:UPDATE");
res2.setResName("客户修改");
resourceDOList.add(res2);
ResourceDO res3 = new ResourceDO();
res3.setResId("CLIENT:DEL");
res3.setResName("客户删除");
resourceDOList.add(res3);
ResourceDO res4 = new ResourceDO();
res4.setResId("CLIENT:QUERY");
res4.setResName("客户查询");
resourceDOList.add(res1);
resourceDOList.add(res4);
return resourceDOList;
}
}
controller层
@RestController
@RequestMapping("admin")
public class BaseController {
/**
* 登陆接口
* @param username
* @param password
*/
@PostMapping("login")
public void login(String username, String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(false);
SecurityUtils.getSubject().login(token);
}
@RequiresPermissions("CLIENT:QUERY")
@GetMapping("user/query")
public List<UserDO> queryUser(Long page, Long pageSize, String username) {
System.out.println("客户信息查询开始");
System.out.println("客户信息查询结束");
return new ArrayList<>();
}
@RequiresPermissions("CLIENT:UPDATE")
@PutMapping("user/update")
public void updateUser(UserDO user) {
System.out.println("客户信息修改开始");
System.out.println("客户信息修改结束");
}
@RequiresPermissions("CLIENT:DEL")
@DeleteMapping("user/del")
public void delUser(Long id) {
System.out.println("客户信息删除开始");
System.out.println("客户信息删除结束");
}
@RequiresPermissions("CLIENT:ADD")
@PostMapping("user/add")
public void addUser(Long id) {
System.out.println("客户信息新增开始");
System.out.println("客户信息新增结束");
}
}
这里的@RequiresPermissions("xxx")
注解,是用于权限验证的,有这个注解标识的,客户端请求时候就会去执行realm里的权限验证方法。
shiro配置
-
shiroConfig
/** * shiro配置类 */ @Configuration public class ShiroConfig { /** * 核心工厂类 配置url过滤、安全管理器等信息 * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 至上而下判断 // url拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 以下放行 filterChainDefinitionMap.put("/admin/login", "anon"); filterChainDefinitionMap.put("/admin/user/query", "anon"); filterChainDefinitionMap.put("/anon/**", "anon"); // 其他不放行 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // login url shiroFilterFactoryBean.setLoginUrl("/admin/login"); // authc shiroFilterFactoryBean.getFilters().put("authc", new MyShiroFilter()); return shiroFilterFactoryBean; } /** * 安全管理器 * @return */ @Bean public SecurityManager securityManager(MyShiroRealm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } /** * shiro域 * @return */ @Bean public MyShiroRealm myShiroRealm(MyCredentialMatcher matcher) { MyShiroRealm realm = new MyShiroRealm(); realm.setCredentialsMatcher(matcher); return realm; } /** * 密码比较器 * @return */ @Bean public MyCredentialMatcher myCredentialMatcher() { return new MyCredentialMatcher(); } /** * 权限验证aop注解支持 * @param manager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager manager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(manager); return advisor; } @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean @DependsOn({ "lifecycleBeanPostProcessor" }) public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { // 设置代理类 DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } }
最后两个Bean是为了让项目支持shiro的aop注解。如果项目中已经引入了
spring-boot-starter-aop
的依赖,则可以不需要配置这两个Bean。这里是个坑,当时也研究了很久。 -
MyRealm
/** * shiro域——身份验证、权限验证 */ public class MyShiroRealm extends AuthorizingRealm { @Autowired private BaseService baseService; /** * 权限 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserDO user = (UserDO)principalCollection.getPrimaryPrincipal(); List<ResourceDO> resourceDOList = baseService.selectResourceByUsername(user.getUsername()); Set<String> collect = resourceDOList.stream().map(ResourceDO::getResId).collect(Collectors.toSet()); authorizationInfo.setStringPermissions(collect); return authorizationInfo; } /** * 身份 * @param token * @return * @throws AuthenticationException * * */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UserDO user = baseService.selectByUsername((String)token.getPrincipal()); if (user == null) { throw new AuthenticationException("用户名或密码错误"); } return new SimpleAuthenticationInfo( user, // 用户 token.getCredentials(), // 表单输入的密码 getName() // realm name ); } }
-
MyShiroFilter
public class MyShiroFilter extends FormAuthenticationFilter { @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { boolean loginRequest = this.isLoginRequest(request, response); HttpServletResponse res = (HttpServletResponse) response; String message = "未登录,请先登陆"; res.addHeader("Content-Type", "text/html;CHARSET=UTF-8"); res.addHeader("sessionstatus", "timeout"); PrintWriter out = response.getWriter(); out.write(message); out.close(); return false; } }
-
MyCredentialMatcher
/** * 密码比较器 如果密码加密方式比较简单 也可以不需要这个比较器 直接在realm里边完成验证 */ public class MyCredentialMatcher extends SimpleCredentialsMatcher { /** * @param authenticationToken * @param info realm里边身份验证通过后的认证信息 * @return */ @Override public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo info) { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; UserDO user = (UserDO)info.getPrincipals().getPrimaryPrincipal(); String pwdInput = new String(token.getPassword()); // 项目中密码加密的方式 String password = new Md5Hash(pwdInput).toHex(); //if (!user.getPassword().equals(password)) { //throw new AuthenticationException("用户名或密码错误"); //} return true; } }
按照上述配置,项目里就可以整合进shiro了。整合进shiro的主要优势,个人感觉最大的就是权限管理逻辑清晰、拓展方便。