使用v-cloak禁止显示渲染代码的元
677 2023-04-03 03:52:10
我们做web项目的过程中,都涉及到登录和授权,那么我们采用一种适合自己的安全框架就很重要了,本章节主要讲springboot+shiro(单机版)
1.1.基本功能点
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。其基本功能点如下图所示:
记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。
1.2架构图
1.3核心的过滤器
hiro提供多个默认的过滤器,我们可以用这些过滤器来配置控制指定URL的权限,Shiro常见的过滤器如下:
配置缩写对应的过滤器功能身份验证相关的
pom依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version></dependency><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version></dependency>
ShiroConfig
Configurationpublic class ShiroConfig { /** * 单机环境,session交给shiro管理 */ @Bean public DefaultWebSessionManager sessionManager(@Value("${renren.globalSessionTimeout:3600}") long globalSessionTimeout){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setSessionIdUrlRewritingEnabled(false); sessionManager.setSessionValidationInterval(globalSessionTimeout * 1000); sessionManager.setGlobalSessionTimeout(globalSessionTimeout * 1000); return sessionManager; } @Bean("securityManager") public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setCacheManager(new EhCacheManager()); securityManager.setRealm(userRealm); securityManager.setSessionManager(sessionManager); securityManager.setRememberMeManager(null); return securityManager; } @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); shiroFilter.setLoginUrl("/login.html"); shiroFilter.setUnauthorizedUrl("/"); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/swagger/**", "anon"); filterMap.put("/v2/api-docs", "anon"); filterMap.put("/swagger-ui.html", "anon"); filterMap.put("/webjars/**", "anon"); filterMap.put("/swagger-resources/**", "anon"); filterMap.put("/statics/**", "anon"); filterMap.put("/login.html", "anon"); filterMap.put("/sys/login", "anon"); filterMap.put("/favicon.ico", "anon"); filterMap.put("/captcha.webp", "anon"); filterMap.put("/**", "authc"); filterMap.put("/**", "perms"); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; }}
UserRealm 自定义认证授权
@Componentpublic class UserRealm extends AuthorizingRealm { @Autowired private SysUserDao sysUserDao; @Autowired private SysMenuDao sysMenuDao; /** * 授权(验证权限时调用) */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SysUserEntity user = (SysUserEntity)principals.getPrimaryPrincipal();Long userId = user.getUserId();List<String> permsList;//系统管理员,拥有最高权限if(userId == Constant.SUPER_ADMIN){List<SysMenuEntity> menuList = sysMenuDao.selectList(null);permsList = new ArrayList<>(menuList.size());for(SysMenuEntity menu : menuList){permsList.add(menu.getPerms());}}else{permsList = sysUserDao.queryAllPerms(userId);}//用户权限列表Set<String> permsSet = new HashSet<>();for(String perms : permsList){if(StringUtils.isBlank(perms)){continue;}permsSet.addAll(Arrays.asList(perms.trim().split(",")));}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.setStringPermissions(permsSet);return info;}/** * 认证(登录时调用) */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {UsernamePasswordToken token = (UsernamePasswordToken)authcToken;//查询用户信息SysUserEntity user = sysUserDao.selectOne(new QueryWrapper<SysUserEntity>().eq("username", token.getUsername()));//账号不存在if(user == null) {throw new UnknownAccountException("账号或密码不正确");}//账号锁定if(user.getStatus() == 0){throw new LockedAccountException("账号已被锁定,请联系管理员");}SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());return info;} @Overridepublic void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();shaCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);shaCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);super.setCredentialsMatcher(shaCredentialsMatcher);}
过滤器配置
主要是增加shirofilter到ioc容器内
@Configurationpublic class FilterConfig { @Bean public FilterRegistrationBean shiroFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new DelegatingFilterProxy("shiroFilter")); //该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 registration.addInitParameter("targetFilterLifecycle", "true"); registration.setEnabled(true); registration.setOrder(Integer.MAX_VALUE - 1); registration.addUrlPatterns("/*"); return registration; } @Bean public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setFilter(new XssFilter()); registration.addUrlPatterns("/*"); registration.setName("xssFilter"); registration.setOrder(Integer.MAX_VALUE); return registration; }
上述搭建的环境只是简单的可以认证,很多小伙伴都很好奇的问,那我授权怎么做呢?我们将从2中解决方案来解决
方案1 使用注解方式 shiroConfig
@Bean("lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 开始shiro的权限注解 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
RequiresPermissions
/** * 所有用户列表 */@RequestMapping("/list")@RequiresPermissions("sys:user:list")public R list(@RequestParam Map<String, Object> params){PageUtils page = sysUserService.queryPage(params);return R.ok().put("page", page);}
shiro支持四种权限的注解:
This annotation basically ensures that subject.isAuthenticated() === true
方案2 使用授权过滤器
官方提供了很多授权过滤器
上面的过滤器介绍中介绍了,
那我们怎么使用呢?我们自己自定义授权过滤器哈.比如我们现在有个需求,要根据url来判断是否有访问的权限,或者其他的需求呢,反正就是官方提供的过滤器不符合我们的要求,那么我们就可以自己完成自己的过滤器。
自定义权限过滤器
public class CustomAutorizatioinFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { //获取请求的url String servletPath = ((HttpServletRequest) request).getServletPath(); Subject subject = SecurityUtils.getSubject(); PrincipalCollection principals = subject.getPrincipals(); //可以从db或者redis中获取你拥有的权限,然后判断是否有权限 //比如从reids中获取改用户的可以访问的url List<String> urls = Lists.newArrayList("a","b"); if (urls.contains(servletPath)) { return true; } return true; }}
ShiroConfig
@Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); // 自定义拦截器 Map<String, Filter> customisedFilter = new HashMap<>(); customisedFilter.put("url", new CustomAutorizatioinFilter()); shiroFilter.setLoginUrl("/login.html"); shiroFilter.setUnauthorizedUrl("/"); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/swagger/**", "anon"); filterMap.put("/v2/api-docs", "anon"); filterMap.put("/swagger-ui.html", "anon"); filterMap.put("/webjars/**", "anon"); filterMap.put("/swagger-resources/**", "anon"); filterMap.put("/statics/**", "anon"); filterMap.put("/templates/**", "anon"); filterMap.put("/modules/**", "anon"); filterMap.put("/login.html", "anon"); filterMap.put("/sys/login", "anon"); filterMap.put("/favicon.ico", "anon"); filterMap.put("/captcha.webp", "anon"); filterMap.put("/**", "authc"); //除了anno所有的请求都是该权限过滤器 filterMap.put("/**", "url"); shiroFilter.setFilters(customisedFilter); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; }
如果我们不想使用cookie来传递session,我可以使用其他的方式么?
解决方案 DefaultWebSessionManager
查看DefaultWebSessionManager的源码我们可以看到如下的代码
@Override protected void onStart(Session session, SessionContext context) { super.onStart(session, context); if (!WebUtils.isHttp(context)) { log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " + "pair. No session ID cookie will be set."); return; } HttpServletRequest request = WebUtils.getHttpRequest(context); HttpServletResponse response = WebUtils.getHttpResponse(context); if (isSessionIdCookieEnabled()) { Serializable sessionId = session.getId(); storeSessionId(sessionId, request, response); } else { log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId()); } request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE); }
然后我们可以看到有个方法:storeSessionId,代码如下:
private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) { if (currentId == null) { String msg = "sessionId cannot be null when persisting for subsequent requests."; throw new IllegalArgumentException(msg); } Cookie template = getSessionIdCookie(); Cookie cookie = new SimpleCookie(template); String idString = currentId.toString(); cookie.setValue(idString); cookie.saveTo(request, response); log.trace("Set session ID cookie for session with id {}", idString); }
上面得代码分析可得到,session放在了cookie里。
获取sessionId的代码如下:
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) { String id = getSessionIdCookieValue(request, response); if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE); } else { //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting): //try the URI path segment parameters first: id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME); if (id == null) { //not a URI path segment parameter, try the query parameters: String name = getSessionIdName(); id = request.getParameter(name); if (id == null) { //try lowercase: id = request.getParameter(name.toLowerCase()); } } if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); } } if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); //automatically mark it valid here. If it is invalid, the //onUnknownSession method below will be invoked and we'll remove the attribute at that time. request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); } // always set rewrite flag - SHIRO-361 request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled()); return id; }
通过上述的思路,我们就可以自定义DefaultWebSessionManager该类的storeSessionId,和getReferencedSessionId方法
public class CustomDefaultWebSessionManager extends DefaultWebSessionManager { private static final Logger log = LoggerFactory.getLogger(CustomDefaultWebSessionManager.class); private final String X_AUTH_TOKEN = "x-auth-token"; // 请求头中获取 sessionId 并把sessionId 放入 response 中 private String getSessionIdHeaderValue(ServletRequest request, ServletResponse response) { if (!(request instanceof HttpServletRequest)) { log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null."); return null; } else { HttpServletRequest httpRequest = (HttpServletRequest) request; // 在request 中 读取 x-auth-token 信息 作为 sessionId String sessionId = httpRequest.getHeader(this.X_AUTH_TOKEN); // 每次读取之后 都把当前的 sessionId 放入 response 中 HttpServletResponse httpResponse = (HttpServletResponse) response; if (StringUtils.isNotEmpty(sessionId)) { httpResponse.setHeader(this.X_AUTH_TOKEN, sessionId); log.info("Current session ID is {}", sessionId); } return sessionId; } } //获取sessionid private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) { String id = this.getSessionIdHeaderValue(request, response); //DefaultWebSessionManager 中代码 直接copy过来 if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header"); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); } //不会把sessionid放在URL后 request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, Boolean.FALSE); return id; }}
然后再shiroConfig中加入我们自定义的session管理类
public DefaultWebSessionManager sessionManager(@Value("${renren.globalSessionTimeout:3600}") long globalSessionTimeout){ CustomDefaultWebSessionManager sessionManager = new CustomDefaultWebSessionManager(); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setSessionIdUrlRewritingEnabled(false); sessionManager.setSessionValidationInterval(globalSessionTimeout * 1000); sessionManager.setGlobalSessionTimeout(globalSessionTimeout * 1000); return sessionManager; }
参考:
blog.csdn.net/weixin_3027…
www.bbsmax.com/A/mo5kKKAQ5…