SpringBoot中配置Shiro

论坛 期权论坛 脚本     
匿名技术用户   2020-12-21 19:53   30   0

习惯用eclipse开发
创建springboot项目前,先安装spring tool插件
打开 help->Eclipse Marketplace
搜索spring找到Spring Tools 3 Add-On插件安装,并重启eclipse
这时候新建项目可以找到Spring Boot选项选择New Spring Starter Project
填写项目名等信息
Next进入依赖选择,一般都会用到Web依赖
点击Finish创建项目,可以在根路径下找到xxxApplication.java的启动类,执行main方法就可以启动项目了

下面说明shiro的配置
首先引入依赖

  <dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>1.2.3</version>
  </dependency>

使用@Configuration与@Bean注解,添加shiro基本配置
Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)
securityManager是必须的。
在shiroFilter中:
loginUrl:没有登录的用户请求需要登录的页面时自动跳转到登录页面。
successUrl:登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
unauthorizedUrl:没有权限默认跳转的页面。

如果需要自定义一个过滤器
为了保持过滤器的执行顺序,创建一个LinkedHashMap

@Configuration
public class ShiroConfig {

 @Bean
 public ShiroFilterFactoryBean shiroFilter() {
  ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
  shiroFilter.setSecurityManager(securityManager());
  shiroFilter.setLoginUrl("/xxx");
  shiroFilter.setUnauthorizedUrl("/user/unauthorized");
  
  Map<String, String> filterChain = new LinkedHashMap<>();
  filterChain.put("/user/logout", "logout");
  filterChain.put("/user/login", "anon");
  filterChain.put("/user/register", "anon");
  filterChain.put("/user/unauthorized", "anon");

//  filterChain.put("/**", "anon");
  filterChain.put("/**", "token");
  shiroFilter.setFilterChainDefinitionMap(filterChain);
  
  Map<String, Filter> filters = new LinkedHashMap<>();
//  filters.put("urlPerms", permFilter());
  filters.put("token", tokenFilter());
  shiroFilter.setFilters(filters);
  return shiroFilter;
 }
 
 /**
  * 此处不应将自定义Filter注册为 @Bean 否则SpringBoot将加载此Filter导致ShiroFilter优先级失效等一系列问题
  * http://www.hillfly.com/2017/179.html
  * @return
  */
 public MyTokenFilter tokenFilter() {
  return new MyTokenFilter();
 }
 @Bean
 public DefaultWebSecurityManager securityManager() {
  DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  
  securityManager.setRealm(shiroRealm());
  
  return securityManager;
 }
 
 @Bean
 public MyShiroRealm shiroRealm() {
  MyShiroRealm realm = new MyShiroRealm();
  realm.setCredentialsMatcher(hashedCredentialsMatcher());
  //没有配置权限缓存,所以关闭授权缓存域
  realm.setAuthorizationCachingEnabled(false);
  return realm;
 }

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
     * HashedCredentialsMatcher说明:
     * 用户传入的token先经过shiroRealm的doGetAuthenticationInfo方法
     * 此时token中的密码为明文。
     * 再经由HashedCredentialsMatcher加密password与查询用户的结果password做对比。
     * new SimpleHash("SHA-256", password, null, 1024).toHex();
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("SHA-256");//散列算法:这里使用SHA-256算法;
        hashedCredentialsMatcher.setHashIterations(1024);//散列的次数,比如散列两次,相当于 MD5(MD5(""));
        return hashedCredentialsMatcher;
    }
}

另外我们还需要自定义ShiroRealm
realm,即 域。
SecurityManager通过realm来验证用户的身份和权限
doGetAuthorizationInfo为授权方法-用于查询用户权限、赋权
doGetAuthenticationInfo为认证方法-subject.login(token);执行时验证用户名密码

public class MyShiroRealm extends AuthorizingRealm{

 static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
   
 @Autowired
 UserService userService;
 
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
  return null;
 }

 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
  UsernamePasswordToken token = (UsernamePasswordToken) arg0;
  String username = token.getUsername();
  logger.info("username = {}", username);
  User user = userService.getUserByName(username);
  logger.info("{}", null!=user?user.toJson():"null");
  if(null != user) {
   SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
   return info;
  }
  return null;
 }

}

另外我还用到了一个过滤器,用作权限验证
isAccessAllowed方法中检验用户是否有权限获取某一资源
AntPathMatcher实现了路径通配符,例如user下所有资源 /user/**
这里使用cookie和缓存来校验用户
仅做参考
当权限被拒绝的时候,执行onAccessDenied
由于这个过滤器在shiro配置中没有注册
使用spring bean的时候需要用到通过ApplicationContext获取bean

public class MyTokenFilter extends AccessControlFilter {

 /**
  *  项目启动,该类在bean注册前初始化,会报空指针, 所以, 需要使用的时候,在代码中用SpringUtil注入。
  */
 private RoleUrlService roleUrlService;
 private UserService userService;
 private EhcacheUtil ehcacheUtil;

 @Override
 protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
   throws Exception {
  String permission = WebUtils.getPathWithinApplication(WebUtils.toHttp(request)).substring(0);
  if (StringUtils.isEmpty(permission)) {
   return true;
  }

  // 公共权限验证
  roleUrlService = SpringUtil.getBean(RoleUrlService.class);
  List<String> publicRole = roleUrlService.getPublicRole();
  PatternMatcher matcher = new AntPathMatcher();
  for (String uri : publicRole) {
   if (null != uri && matcher.matches(uri, permission)) {
    return true;
   }
  }

  // Token验证
  HttpServletRequest rq = (HttpServletRequest) request;
  Cookie token = null;
  Cookie[] cookies = rq.getCookies();
  if(null == cookies) {
   return false;
  }
  for (Cookie cookie : cookies) {
   if ("token".equals(cookie.getName())) {
    token = cookie;
    break;
   }
  }
  if (null == token) {
   return false;
  }
  ehcacheUtil = SpringUtil.getBean(EhcacheUtil.class);
  UsernamePasswordToken upToken = (UsernamePasswordToken) ehcacheUtil
    .get(EhCacheConstants.TOKEN_PREFIX + token.getValue());
  if (null == upToken) {
   return false;
  }
  userService = SpringUtil.getBean(UserService.class);
  List<String> urlList = userService.findUserUrl(upToken.getUsername());
  for (String uri : urlList) {
   if (null != uri && matcher.matches(uri, permission)) {
    return true;
   }
  }
  return false;
 }

 @Override
 protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
  Subject subject = getSubject(request, response);
  if (!subject.isAuthenticated()) {
   authenticationFailed(response);
   return false;
  }
  return true;
 }

 /**
  * 认证失败
  *
  * @param response
  * @throws IOException
  */
 private void authenticationFailed(ServletResponse response) throws IOException {
  response.setContentType("text/html;charset=UTF-8");
  HttpServletResponse httpResponse = (HttpServletResponse) response;
  httpResponse.getWriter().write(JSON.toJSONString(Result.notLogin()));
 }
}

最后我们需要一个登陆方法

 @PostMapping("login")
 public Result<String> login(HttpServletResponse response, UsernamePasswordToken token) {
  Subject subject = SecurityUtils.getSubject();

  try {
   subject.login(token);
   UUID uuid = UUID.randomUUID();
   // 把用户登录信息存入缓存 key值为 TOKEN_{用户标识}
   ehcacheUtil.put(EhCacheConstants.TOKEN_PREFIX + uuid.toString(), token);
   response.addCookie(new Cookie("token", uuid.toString()));
   return new Result<>("登录成功");
  } catch (IncorrectCredentialsException e) {
   return new Result<>("密码错误");
  } catch (LockedAccountException e) {
   return new Result<>("登录失败,该用户已被冻结");
  } catch (AuthenticationException e) {
   return new Result<>("该用户不存在");
  } catch (Exception e) {
   e.printStackTrace();
  }
  return new Result<>("登录错误");
 }
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP