搜索词>>权限控制 耗时0.1890
  • spring boot shiro redis整合基于角色和权限的安全管理-Java编程

    Java编程之spring boot shiro redis整合基于角色和权限的安全管理,Java编程,spring boot,shiro,权限控制<h2>一、概述</h2>   本博客主要讲解spring boot整合Apache的shiro框架,实现基于角色的安全访问控制或者基于权限的访问安全控制,其中还使用到分布式缓存redis进行用户认证信息的缓存,减少数据库查询的开销。Apache shiro与spring security的作用几乎一样都是简化了Java程序的权限控制开发。 <h2>二、项目</h2> <h3>2.1首先是通过eclipse创建一个最新的spring boot项目,并添加以下依赖:</h3> <strong>pom.xml</strong> <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project.demo.shiro</groupId> <artifactId>demo-springboot-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot-shiro-hello</name> <description>demo-springboot-shiro-hello</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- shiro权限控制框架 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <!--缓存暂时用简单的 https://mvnrepository.com/artifact/org.ehcache/ehcache --> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib --> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> <!-- redis缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> <h3>2.2配置redis</h3> application.properties文件中添加配置: <pre> <code>####################Redis 配置信息 ########################## # Redis数据库分片索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=10.1.1.2 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=0 #测试redis的缓存日志 logging.level.net.xqlee.project.demo.shiro.config.shiro.cache=DEBUG</code></pre> <br /> Java config的redis配置<br /> <strong>RedisConfig</strong> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.redis; import java.lang.reflect.Method; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration @EnableCaching // 继承CachingConfigurerSupport并重写方法,配合该注解实现spring缓存框架的启用 public class RedisConfig extends CachingConfigurerSupport { /** 载入通过配置文件配置的连接工场 **/ @Autowired RedisConnectionFactory redisConnectionFactory; @SuppressWarnings("rawtypes") @Autowired RedisTemplate redisTemplate; @Bean RedisTemplate<String, Object> objRedisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } /* * (non-Javadoc) * * @see org.springframework.cache.annotation.CachingConfigurerSupport# * cacheManager() */ @Bean // 必须添加此注解 @Override public CacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate); // 设置缓存过期时间 // redisCacheManager.setDefaultExpiration(60);//秒 return redisCacheManager; } /** * 重写缓存的key生成策略,可根据自身业务需要进行自己的配置生成条件 * * @see org.springframework.cache.annotation.CachingConfigurerSupport# * keyGenerator() */ @Bean // 必须项 @Override public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } } </code></pre> <h3><br /> 2.3创建shiro需要的 redis缓存器和缓存管理实现类</h3> 首先是缓存器cache的实现<br /> RedisCache.java <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro.cache; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; /** * Redis的Shiro缓存对象实现 * * @author xq * * @param <K> * @param <V> */ public class RedisCache<K, V> implements Cache<K, V> { private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); private RedisTemplate<K, V> redisTemplate; private final static String PREFIX = "shiro-cache:"; private String cacheKey; private long globExpire = 30; @SuppressWarnings({ "rawtypes", "unchecked" }) public RedisCache(final String name, final RedisTemplate redisTemplate) { this.cacheKey = PREFIX + name + ":"; this.redisTemplate = redisTemplate; } @Override public V get(K key) throws CacheException { logger.debug("Shiro从缓存中获取数据KEY值["+key+"]"); redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MINUTES); return redisTemplate.boundValueOps(getCacheKey(key)).get(); } @Override public V put(K key, V value) throws CacheException { V old = get(key); redisTemplate.boundValueOps(getCacheKey(key)).set(value); return old; } @Override public V remove(K key) throws CacheException { V old = get(key); redisTemplate.delete(getCacheKey(key)); return old; } @Override public void clear() throws CacheException { redisTemplate.delete(keys()); } @Override public int size() { return keys().size(); } @Override public Set<K> keys() { return redisTemplate.keys(getCacheKey("*")); } @Override public Collection<V> values() { Set<K> set = keys(); List<V> list = new ArrayList<>(); for (K s : set) { list.add(get(s)); } return list; } @SuppressWarnings("unchecked") private K getCacheKey(Object k) { return (K) (this.cacheKey + k); } } </code></pre> 还有缓存管理器:<br /> <strong>RedisCacheManager.java</strong> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro.cache; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; /** * Redis的Shiro缓存管理器实现 * * @author xq * */ public class RedisCacheManager implements CacheManager { @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { return new RedisCache<>(name, redisTemplate); } } </code></pre> <h3>2.4自定义一个shiro的realm实现</h3> <h3>创建realm之前需要编写一个模拟数据库查询的用户业务处理类,提供给上面的自定义realm使用</h3> 简单的用户登录对象: <pre> <code class="language-java">package net.xqlee.project.demo.shiro.pojo; import java.util.Date; import java.util.List; /** * 用户信息 * * @author xqlee * */ public class LoginAccount { /** 用户名 */ String loginName; List<String> roles;// 测试用 List<String> permissions;// 测试用直接放用户登录对象里面 /** 用户密码 **/ String password; boolean enabled; Date createDate; boolean isExpired; public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public boolean isExpired() { return isExpired; } public void setExpired(boolean isExpired) { this.isExpired = isExpired; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } public List<String> getPermissions() { return permissions; } public void setPermissions(List<String> permissions) { this.permissions = permissions; } } </code></pre> 用户模拟业务处理 <pre> <code class="language-java">package net.xqlee.project.demo.shiro.service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.stereotype.Component; import net.xqlee.project.demo.shiro.pojo.LoginAccount; /** * 用户业务服务类 * * @author xqlee * */ @Component("userService") public class UserService { /** 由于重点不在数据库,这里需要使用数据库的地方全部用map代替 **/ /** 用户信息 **/ static Map<String, LoginAccount> users = new HashMap<>(); static { // 创建一个用户 LoginAccount account = new LoginAccount(); account.setLoginName("leftso"); account.setPassword("123456"); account.setEnabled(true); account.setExpired(false); // 角色添加 List<String> roles = new ArrayList<>(); roles.add("ROLE_USER"); account.setRoles(roles); List<String> permissions = new ArrayList<>(); permissions.add("query"); permissions.add("delete"); account.setPermissions(permissions); users.put(account.getLoginName(), account); // 创建一个用户 LoginAccount admin = new LoginAccount(); admin.setLoginName("admin"); admin.setPassword("123456"); admin.setEnabled(true); admin.setExpired(false); // 角色添加 roles = new ArrayList<>(); roles.add("ROLE_ADMIN"); admin.setRoles(roles); permissions = new ArrayList<>(); permissions.add("query"); permissions.add("delete"); admin.setPermissions(permissions); users.put("admin", admin); } /** * 通过用户名获取用户权限集合 * * @param loginName * 用户名 * @return 用户的权限集合 */ public List<String> getPermissionsByLoginName(String loginName) { if (users.containsKey(loginName)) { return users.get(loginName).getPermissions(); } return new ArrayList<>(); } /** * 通过用户名获取用户信息 * * @param loginName * 用户名 * @return 用户信息 */ public LoginAccount getLoginAccountByLoginName(String loginName) { if (users.containsKey(loginName)) { return users.get(loginName); } return null; } } </code></pre>   <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro; import java.util.List; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import net.xqlee.project.demo.shiro.pojo.LoginAccount; import net.xqlee.project.demo.shiro.service.UserService; /** * 实现一个基于JDBC的Realm,继承AuthorizingRealm可以看见需要重写两个方法,doGetAuthorizationInfo和doGetAuthenticationInfo * * @author xqlee * */ @Component("JDBCShiroRealm") public class JDBCShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(JDBCShiroRealm.class); /*** 用户业务处理类,用来查询数据库中用户相关信息 ***/ @Autowired UserService userService; /*** * 获取用户授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("##################执行Shiro权限认证##################"); // 获取用户名 String loginName = (String) principalCollection.fromRealm(getName()).iterator().next(); // 判断用户名是否存在 if (StringUtils.isEmpty(loginName)) { return null; } // 查询登录用户信息 LoginAccount account = userService.getLoginAccountByLoginName(loginName); if (account == null) { logger.warn("用户[" + loginName + "]信息不存在"); return null; } // 创建一个授权对象 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 进行权限设置 List<String> permissions = account.getPermissions(); if (permissions != null && !permissions.isEmpty()) { info.addStringPermissions(permissions); } // 角色设置 List<String> roles = account.getRoles(); if (roles != null) { info.addRoles(roles); } return info; } /** * 获取用户认证信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("##################执行Shiro登陆认证##################"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; // 通过表单接收的用户名 String loginName = token.getUsername(); if (loginName != null && !"".equals(loginName)) { // 模拟数据库查询用户信息 LoginAccount account = userService.getLoginAccountByLoginName(loginName); if (account != null) { // 登陆的主要信息: 可以是一个实体类的对象, 但该实体类的对象一定是根据 token 的 username 查询得到的. Object principal = token.getPrincipal(); // 创建shiro的用户认证对象 // 注意该对象的密码将会传递至后续步骤与前面登陆的subject的密码进行比对。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, account.getPassword(), getName()); return authenticationInfo; } } return null; } } </code></pre> <h3><br /> <br /> 2.5shiro的核心配置文件</h3> <strong>ShiroConfig.java</strong> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.SecurityUtils; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import net.xqlee.project.demo.shiro.config.shiro.cache.RedisCacheManager; /*** * shiro权限管理配置 * * @author xqlee * */ @Configuration public class ShiroConfig { /** * ehcache缓存方案<br/> * 简单的缓存,后续可更换为redis缓存,通过自己实现shiro的CacheManager接口和Cache接口 * * @return */ @Bean public CacheManager shiroEhCacheManager() { EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return cacheManager; } /** * redis缓存方案 * * @return */ @Bean public CacheManager shiroRedisCacheManager() { return new RedisCacheManager(); } /**** * 自定义Real * * @return */ @Bean public JDBCShiroRealm jdbcShiroRealm() { JDBCShiroRealm realm = new JDBCShiroRealm(); // 根据情况使用缓存器 realm.setCacheManager(shiroRedisCacheManager());//shiroEhCacheManager() return realm; } /*** * 安全管理配置 * * @return */ @Bean public SecurityManager defaultWebSecurityManager() { // DefaultSecurityManager defaultSecurityManager = new // DefaultSecurityManager(); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 注意:!!!初始化成这个将会报错java.lang.IllegalArgumentException: // SessionContext must be an HTTP compatible // implementation.:模块化本地测试shiro的一些总结 // 配置 securityManager.setRealm(jdbcShiroRealm()); // 注意这里必须配置securityManager SecurityUtils.setSecurityManager(securityManager); // 根据情况选择缓存器 securityManager.setCacheManager(shiroRedisCacheManager());//shiroEhCacheManager() return securityManager; } /** * 配置shiro的拦截器链工厂,默认会拦截所有请求,并且不可配置 * * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); // 配置安全管理(必须) filterFactoryBean.setSecurityManager(defaultWebSecurityManager()); // 配置登陆的地址 filterFactoryBean.setLoginUrl("/userNoLogin.do");// 未登录时候跳转URL,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 filterFactoryBean.setSuccessUrl("/welcome.do");// 成功后欢迎页面 filterFactoryBean.setUnauthorizedUrl("/403.do");// 未认证页面 // 配置拦截地址和拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 必须使用LinkedHashMap,因为拦截有先后顺序 // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 filterChainDefinitionMap.put("/userNoLogin.do*", "anon");// 未登录跳转页面不设权限认证 filterChainDefinitionMap.put("/login.do*", "anon");// 登录接口不设置权限认真 filterChainDefinitionMap.put("/logout.do*", "anon");// 登出不需要认证 // 以下配置同样可以通过注解 // @RequiresPermissions("user:edit")来配置访问权限和角色注解@RequiresRoles(value={"ROLE_USER"})方式定义 // 权限配置示例,这里的配置理论来自数据库查询 filterChainDefinitionMap.put("/user/**", "roles[ROLE_USER],perms[query]");// /user/下面的需要ROLE_USER角色或者query权限才能访问 filterChainDefinitionMap.put("/admin/**", "perms[ROLE_ADMIN]");// /admin/下面的所有需要ROLE_ADMIN的角色才能访问 // 剩下的其他资源地址全部需要用户认证后才能访问 filterChainDefinitionMap.put("/**", "authc"); filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 全部配置 // anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名访问 // // authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter // 需要登录,不需要权限和角色可访问 // // authcBasic // org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter // // perms // org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter // 需要给定的权限值才能访问 // // port org.apache.shiro.web.filter.authz.PortFilter // // rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter // // roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter // 需要给定的角色才能访问 // // ssl org.apache.shiro.web.filter.authz.SslFilter // // user org.apache.shiro.web.filter.authc.UserFilter // // logout org.apache.shiro.web.filter.authc.LogoutFilter return filterFactoryBean; } } </code></pre> 上面配置中有两个缓存器可以选择,一个是简单的ehcache,一个是redis,大型项目推荐使用redis <h3><br /> 2.6编写一个测试的controller</h3> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.controller; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import net.sf.json.JSONObject; /** * 用户登录用 * * @author xqlee * */ @RestController public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); /**** * 用户未登录 * * @return */ @GetMapping("userNoLogin.do") public Object noLogin() { JSONObject object = new JSONObject(); object.put("message", "用户未登录"); return object; } @GetMapping(value = "/login.do") public String login(String loginName, String password) { try { // 创建shiro需要的token UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password.toCharArray()); usernamePasswordToken.setRememberMe(true);// 记住 try { SecurityUtils.getSubject().login(usernamePasswordToken); } catch (UnknownAccountException uae) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,未知账户"); return "对用户[" + loginName + "]进行登录验证..验证未通过,未知账户"; } catch (IncorrectCredentialsException ice) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误的凭证"); ice.printStackTrace(); return "对用户[" + loginName + "]进行登录验证..验证未通过,错误的凭证"; } catch (LockedAccountException lae) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,账户已锁定"); return "对用户[" + loginName + "]进行登录验证..验证未通过,账户已锁定"; } catch (ExcessiveAttemptsException eae) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误次数过多"); return "对用户[" + loginName + "]进行登录验证..验证未通过,错误次数过多"; } catch (AuthenticationException ae) { // 通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,堆栈轨迹如下"); ae.printStackTrace(); return "用户名或密码不正确"; } return "Login Success!"; } catch (Exception e) { return "登陆时候发生异常," + e.getMessage(); } } @GetMapping("/user/hello.do") public String hello() { return "Hello User, From Server"; } @GetMapping("/admin/hello.do") public String helloAdmin() { return "Hello Admin, From Server"; } @GetMapping("/welcome.do") public String loginSuccess() { return "welcome"; } @GetMapping("/403.do") public Object error403(HttpServletResponse response) { response.setStatus(403); JSONObject object = new JSONObject(); object.put("message", "用户权限不够"); return object; } } </code></pre> <h3>2.7测试</h3> 1.通过spring bootapplication方式启动项目,项目默认监听在8080端口<br /> <br /> 首先访问需要权限的url  http://localhost:8080/user/hello.do<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="访问需要权限的url" src="/resources/assist/images/blog/9399b39fad254f35b9f4ec1d6ec9d671.jpg" /><br /> 可以看到返回的是用户未登录提示,也就是我们在controller中写的未登录时候调用的方法返回值。<br /> <br /> 现在我们先通过用户名leftso和密码123456进行登录  localhost:8080/login.do?loginName=leftso&password=123456<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="登录" src="/resources/assist/images/blog/3281336d3f4844fd8164a2768271cbaa.png" /><br /> <br /> 可以看到登录成功,这个时候我们再次打开最初访问的:http://localhost:8080/user/hello.do<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="成功访问" src="/resources/assist/images/blog/51efce91cb62475bbd70a0554b8b8323.png" /><br /> 可以看到这次我们成功访问了需要ROLE_USER角色的url,<br /> <br /> 现在用这个登录的信息访问需要ROLE_ADMIN权限的地址http://localhost:8080/admin/hello.do<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="提示权限不足" src="/resources/assist/images/blog/42e8ee00ac544ebebccb3f95fd157a5a.png" /><br /> 上面可以看到,无法访问,返回的提示是权限不足<br /> <br /> 切换为admin的用户登录,然后访问地址http://localhost:8080/admin/hello.do<br /> <br /> 首先是admin登录<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="admin登录" src="/resources/assist/images/blog/a470702a46cd4f5d901c4689594df9d3.png" /><br /> 然后访问admin权限的地址<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="admin资源访问成功" src="/resources/assist/images/blog/e16a663b41db45fb887fddd5ede6f3a9.png" /><br /> 上面看到admin也对应的访问成功了。<br /> <br /> 并且,切回eclipse的控制台可以看到:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="redis" src="/resources/assist/images/blog/df831fee0fb74c02aece3421c25b8949.png" /><br /> 我们的redis缓存也起作用了。<br /> <br /> 至此spring boot shiro redis的整合角色和权限控制讲解完毕。<br /> <br /> 留下思考:如何实现spring boot shiro的无状态认证呢?也就是当我们编写一个既要web前后端分离使用也同时需要满足app的接口需求。只能通过无状态的token实现。<br /> <br /> <a target="_blank" href="http://www.leftso.com/blog/593.html">spring boot shiro无状态化配置使用教程点击前往</a>
  • spring boot 入门 整合security jpa角色和权限的实现

    spring boot 入门 整合security jpa角色和权限的实现。本文继续使用spring boot 和Spring Security系列进行注册,并着重于如何正确实现角色和权限。<h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>1.概述</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">本文继续使用spring boot 和Spring Security系列进行注册,并着重于如何正确实现<strong>角色和权限</strong>。</span></span></span><br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>2. <em>用户</em> <em>角色</em>和<em>权限</em></strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">首先,让我们从我们的实体开始。我们有三个主要实体:</span></span></span></p> <ul> <li>User</li> <li>Role -这代表在系统中的用户的高级别角色; 每个角色都将拥有一组低级权限</li> <li>Privilege  -表示系统中一个低级别的,细小的特权/权限</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">User.java:</span></span></span><br />  </p> <pre> <code class="language-java">@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private String password; private boolean enabled; private boolean tokenExpired; @ManyToMany @JoinTable( name = "users_roles", joinColumns = @JoinColumn( name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id")) private Collection<Role> roles; }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">如您所见,用户包含角色,但也包含适当注册机制所需的一些其他详细信息。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">接下来 - 这是<strong>Role.java</strong>:</span></span></span></p> <pre> <code class="language-java">@Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "roles") private Collection<User> users; @ManyToMany @JoinTable( name = "roles_privileges", joinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "privilege_id", referencedColumnName = "id")) private Collection<Privilege> privileges; }</code></pre> 最后<strong>是</strong>Privilege: <pre> <code class="language-java">@Entity public class Privilege { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "privileges") private Collection<Role> roles; }</code></pre> 如您所见,我们正在考虑用户角色以及角色特权关系<strong>多对多双向</strong>。 <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3.设置权限和角色</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">接下来 - 让我们专注于在系统中对权限和角色进行一些早期设置。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们将把它与应用程序的启动绑定在一起,我们将在<em>ContextRefreshedEvent</em>上使用<em>ApplicationListener</em>在服务器启动时加载我们的初始数据:</span></span></span></p> <pre> <code class="language-java">@Component public class InitialDataLoader implements ApplicationListener<ContextRefreshedEvent> { boolean alreadySetup = false; @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private PrivilegeRepository privilegeRepository; @Autowired private PasswordEncoder passwordEncoder; @Override @Transactional public void onApplicationEvent(ContextRefreshedEvent event) { if (alreadySetup) return; Privilege readPrivilege = createPrivilegeIfNotFound("READ_PRIVILEGE"); Privilege writePrivilege = createPrivilegeIfNotFound("WRITE_PRIVILEGE"); List<Privilege> adminPrivileges = Arrays.asList( readPrivilege, writePrivilege); createRoleIfNotFound("ROLE_ADMIN", adminPrivileges); createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege)); Role adminRole = roleRepository.findByName("ROLE_ADMIN"); User user = new User(); user.setFirstName("Test"); user.setLastName("Test"); user.setPassword(passwordEncoder.encode("test")); user.setEmail("test@test.com"); user.setRoles(Arrays.asList(adminRole)); user.setEnabled(true); userRepository.save(user); alreadySetup = true; } @Transactional private Privilege createPrivilegeIfNotFound(String name) { Privilege privilege = privilegeRepository.findByName(name); if (privilege == null) { privilege = new Privilege(name); privilegeRepository.save(privilege); } return privilege; } @Transactional private Role createRoleIfNotFound( String name, Collection<Privilege> privileges) { Role role = roleRepository.findByName(name); if (role == null) { role = new Role(name); role.setPrivileges(privileges); roleRepository.save(role); } return role; } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">那么,在这个简单的设置代码中发生了什么?没有复杂的:</span></span></span></p> <ul> <li>我们正在创建这些权限</li> <li>我们正在创建角色并为其分配权限</li> <li>我们正在创建一个用户并为其分配一个角色</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">请注意我们如何使用<em>alreadySetup</em>标志来<strong>确定安装是否需要运行</strong>。这仅仅是因为,取决于您在应用程序中配置了多少上下文 - <em>ContextRefreshedEvent</em>可能会被多次触发。我们只希望安装程序执行一次。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">这里有两个简短的注释 - <strong>首先是术语</strong>。我们在这里使用<em>权限 - 角色</em>条款,但在Spring中,它们略有不同。在春季,我们的特权被称为角色,也被称为(授权)权力 - 这有点混乱。当然不是一个问题,但绝对值得注意。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><strong>其次 - 这些Spring角色(我们的特权)需要一个前缀</strong> ; 默认情况下,该前缀是“ROLE”,但可以更改。我们在这里并没有使用这个前缀,只是为了保持简单,但请记住,如果您没有明确地改变它,那就是必需的。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>4.自定义<em>UserDetailsS​​ervice</em></strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">现在让我们来看看认证过程。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们将看到如何在我们自定义的<em>UserDetailsS​​ervice中</em>检索用户,以及如何从用户分配的角色和权限映射正确的权限集:</span></span></span></p> <pre> <code class="language-java">@Service("userDetailsService") @Transactional public class MyUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private IUserService service; @Autowired private MessageSource messages; @Autowired private RoleRepository roleRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if (user == null) { return new org.springframework.security.core.userdetails.User( " ", " ", true, true, true, true, getAuthorities(Arrays.asList( roleRepository.findByName("ROLE_USER")))); } return new org.springframework.security.core.userdetails.User( user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, getAuthorities(user.getRoles())); } private Collection<? extends GrantedAuthority> getAuthorities( Collection<Role> roles) { return getGrantedAuthorities(getPrivileges(roles)); } private List<String> getPrivileges(Collection<Role> roles) { List<String> privileges = new ArrayList<>(); List<Privilege> collection = new ArrayList<>(); for (Role role : roles) { collection.addAll(role.getPrivileges()); } for (Privilege item : collection) { privileges.add(item.getName()); } return privileges; } private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) { List<GrantedAuthority> authorities = new ArrayList<>(); for (String privilege : privileges) { authorities.add(new SimpleGrantedAuthority(privilege)); } return authorities; } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">这里有趣的事情是特权(和角色)如何映射到GrantedAuthority实体。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">这种映射使整个安全配置<strong>具有高度的灵活性和强大功能</strong> - 您可以根据需要将角色和权限混合匹配,最终将它们正确映射到权限并返回到框架。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>5. <em>用户</em>注册</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">最后 - 让我们来看看注册一个新用户。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们已经看到安装程序如何创建用户并为其分配角色(和特权) - 现在让我们来看看在注册新用户期间如何完成此操作:</span></span></span><br />  </p> <pre> <code class="language-java">@Override public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException { if (emailExist(accountDto.getEmail())) { throw new EmailExistsException ("There is an account with that email adress: " + accountDto.getEmail()); } User user = new User(); user.setFirstName(accountDto.getFirstName()); user.setLastName(accountDto.getLastName()); user.setPassword(passwordEncoder.encode(accountDto.getPassword())); user.setEmail(accountDto.getEmail()); user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER"))); return repository.save(user); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在这个简单的实现中,我们假设一个标准用户正在被注册,所以<em>ROLE_USER</em>角色被分配给它。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">当然,更复杂的逻辑可以通过相同的方式轻松实现 - 无论是通过多种硬编码注册方法,还是允许客户端发送正在注册的用户类型。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>6,总结</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在本教程中,我们演示了如何使用JPA为Spring boot Spring Security支持的系统实现角色和权限。</span></span></span></p> <blockquote> <p style="margin-left:40px">提示:<a href="http://www.leftso.com/resource/1013.html" target="_blank" >源码下载</a></p> </blockquote>
  • 解决PHP程序以root启动导致部分Apache创建的文件权限不足问题

    解决PHP程序以root启动导致部分Apache创建的文件权限不足问题解决PHP程序以root启动导致部分Apache创建的文件权限不足问题<br /> For CentOS and other Red Hat distros, add the umask setting to /etc/sysconfig/httpd and restart apache. <pre> <code class="language-html">[root ~]$ echo "umask 002" >> /etc/sysconfig/httpd [root ~]$ service httpd restart</code></pre>
  • spring boot Jersey基于角色的安全控制使用JAX-RS的注解

    Spring Boot 基于角色的安全控制使用JAX-RS的注解,spring boot,Jersey<h2>一、摘要说明</h2>     学习使用spring boot和Jersey框架来创建JAX-RS 2.0 REST APIs,并且使用JAX-RS注解来添加基于角色的安全控制。例如注解<code>@PermitAll</code>, <code>@RolesAllowed</code> 或者 <code>@DenyAll。</code><br />    <br />     本博客学习纲要: <ul> <li>项目结构    </li> <li>创建REST api</li> <li>使用jax-rs注释的安全REST api</li> <li>使用jax-rs容器请求过滤器编写安全过滤器</li> <li>一个例子演示</li> </ul> <h2>二、项目结构</h2> 本教程中创建的应用程序的项目结构如下:<br /> <img alt="项目结构" class="img-thumbnail" src="/resources/assist/images/blog/1d2312c3dc4d48f79fb04f5094b9a000.png" /> <h2>三、创建 REST APIs</h2> 1.去 Spring Initializr网站,创建一个spring boot项目并且添加 Jersey (JAX-RS) 依赖<br /> <img alt="创建项目" class="img-thumbnail" src="/resources/assist/images/blog/27643e1e52c3445ca3c603d6184b69bb.png" />2.导入项目到eclipse中<br /> 以zip文件的格式生成项目。在你电脑的某个地方把它取出来。将该项目作为“Existing maven application”导入eclipse。<br /> <br /> 3.检查maven的依赖<br /> 检查maven的依赖文件中必须有<strong>spring-boot-starter-jersey</strong> <pre> <code class="language-xml"><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies></code></pre> 4.创建 REST APIs<br /> 现在创建一些jax-rs资源,我们将访问测试阶段。我已经创建了UserResource类。<br /> <br /> <strong>UserResource.java</strong> <pre> <code class="language-java"> import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "users") @Path("/users") public class UserResource { private static Map<Integer, User> DB = new HashMap<>(); @GET @Produces("application/json") public Users getAllUsers() { Users users = new Users(); users.setUsers(new ArrayList<>(DB.values())); return users; } @POST @Consumes("application/json") public Response createUser(User user) throws URISyntaxException { if(user.getFirstName() == null || user.getLastName() == null) { return Response.status(400).entity("Please provide all mandatory inputs").build(); } user.setId(DB.values().size()+1); user.setUri("/user-management/"+user.getId()); DB.put(user.getId(), user); return Response.status(201).contentLocation(new URI(user.getUri())).build(); } @GET @Path("/{id}") @Produces("application/json") public Response getUserById(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user == null) { return Response.status(404).build(); } return Response .status(200) .entity(user) .contentLocation(new URI("/user-management/"+id)).build(); } @PUT @Path("/{id}") @Consumes("application/json") @Produces("application/json") public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException { User temp = DB.get(id); if(user == null) { return Response.status(404).build(); } temp.setFirstName(user.getFirstName()); temp.setLastName(user.getLastName()); DB.put(temp.getId(), temp); return Response.status(200).entity(temp).build(); } @DELETE @Path("/{id}") public Response deleteUser(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user != null) { DB.remove(user.getId()); return Response.status(200).build(); } return Response.status(404).build(); } static { User user1 = new User(); user1.setId(1); user1.setFirstName("John"); user1.setLastName("Wick"); user1.setUri("/user-management/1"); User user2 = new User(); user2.setId(2); user2.setFirstName("Harry"); user2.setLastName("Potter"); user2.setUri("/user-management/2"); DB.put(user1.getId(), user1); DB.put(user2.getId(), user2); } }</code></pre> <strong>Users.java</strong> <pre> <code class="language-java"> import java.util.ArrayList; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "users") public class Users { @XmlElement(name="user") private ArrayList<User> users; public ArrayList<User> getUsers() { return users; } public void setUsers(ArrayList<User> users) { this.users = users; } }</code></pre> <strong>User.java</strong> <pre> <code class="language-java"> import java.io.Serializable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "user") public class User implements Serializable { private static final long serialVersionUID = 1L; @XmlAttribute(name = "id") private int id; @XmlAttribute(name="uri") private String uri; @XmlElement(name = "firstName") private String firstName; @XmlElement(name = "lastName") private String lastName; // Getters and Setters }</code></pre> 5.配置Jersey<br /> 现在我们有了一个jax-rs资源,我们希望从spring启动应用程序中访问它,其中包括Jersey依赖项。让我们将这个资源注册为泽西资源。 <pre> <code class="language-java">import org.glassfish.jersey.server.ResourceConfig; import org.springframework.stereotype.Component; @Component public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(SecurityFilter.class); register(UserResource.class); } }</code></pre>   <ul> <li>查看@component注释。它使这个类可以注册,而spring引导自动扫描源文件夹中的java类。</li> <li>资源econfig提供了简化jax-rs组件注册的高级功能。</li> <li>SecurityFilter类是实际的auth细节处理器,我们将在本教程后面看到。</li> </ul> <br /> 使用SpringBootServletInitializer扩展spring应用程序 <pre> <code class="language-java">import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.support.SpringBootServletInitializer; @SpringBootApplication public class JerseydemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { new JerseydemoApplication().configure(new SpringApplicationBuilder(JerseydemoApplication.class)).run(args); } }</code></pre> <h2>四、使用JAX-RS Annotations创建安全的REST APIs</h2> 现在,当我们的api准备好时,我们将开始保护它们。让我们使用jax-rs注释对api进行注释,基于它们所需的访问级别和允许访问它们的用户角色。 <pre> <code class="language-java">@XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "users") @Path("/users") public class UserResource { private static Map<Integer, User> DB = new HashMap<>(); @GET @PermitAll @Produces("application/json") public Users getAllUsers() { Users users = new Users(); users.setUsers(new ArrayList<>(DB.values())); return users; } @POST @Consumes("application/json") @RolesAllowed("ADMIN") public Response createUser(User user) throws URISyntaxException { if(user.getFirstName() == null || user.getLastName() == null) { return Response.status(400).entity("Please provide all mandatory inputs").build(); } user.setId(DB.values().size()+1); user.setUri("/user-management/"+user.getId()); DB.put(user.getId(), user); return Response.status(201).contentLocation(new URI(user.getUri())).build(); } @GET @Path("/{id}") @Produces("application/json") @PermitAll public Response getUserById(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user == null) { return Response.status(404).build(); } return Response .status(200) .entity(user) .contentLocation(new URI("/user-management/"+id)).build(); } @PUT @Path("/{id}") @Consumes("application/json") @Produces("application/json") @RolesAllowed("ADMIN") public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException { User temp = DB.get(id); if(user == null) { return Response.status(404).build(); } temp.setFirstName(user.getFirstName()); temp.setLastName(user.getLastName()); DB.put(temp.getId(), temp); return Response.status(200).entity(temp).build(); } @DELETE @Path("/{id}") @RolesAllowed("ADMIN") public Response deleteUser(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user != null) { DB.remove(user.getId()); return Response.status(200).build(); } return Response.status(404).build(); } static { User user1 = new User(); user1.setId(1); user1.setFirstName("John"); user1.setLastName("Wick"); user1.setUri("/user-management/1"); User user2 = new User(); user2.setId(2); user2.setFirstName("Harry"); user2.setLastName("Potter"); user2.setUri("/user-management/2"); DB.put(user1.getId(), user1); DB.put(user2.getId(), user2); } }</code></pre> 你可以看到注解 角色注解@RolesAllowed <h2>五、使用JAX-RS ContainerRequestFilter来编写security filter</h2>   现在是编写我们的安全过滤器的时候了,它将检查传入的请求,获取授权信息(在本例中是基本身份验证),然后将匹配用户名和密码,最后它将通过它的角色来验证用户的访问级别。如果一切都匹配,API将被访问,否则用户将获得访问被拒绝的响应。 <pre> <code class="language-java">import java.lang.reflect.Method; import java.util.Arrays; import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Context; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; /** * This filter verify the access permissions for a user based on * user name and password provided in request * */ @Provider public class SecurityFilter implements javax.ws.rs.container.ContainerRequestFilter { private static final String AUTHORIZATION_PROPERTY = "Authorization"; private static final String AUTHENTICATION_SCHEME = "Basic"; private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).build(); private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN).build(); private static final Response SERVER_ERROR = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); @Context private ResourceInfo resourceInfo; @Override public void filter(ContainerRequestContext requestContext) { Method method = resourceInfo.getResourceMethod(); //Access allowed for all if( ! method.isAnnotationPresent(PermitAll.class)) { //Access denied for all if(method.isAnnotationPresent(DenyAll.class)) { requestContext.abortWith(ACCESS_FORBIDDEN); return; } //Get request headers final MultivaluedMap<String, String> headers = requestContext.getHeaders(); //Fetch authorization header final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY); //If no authorization information present; block access if(authorization == null || authorization.isEmpty()) { requestContext.abortWith(ACCESS_DENIED); return; } //Get encoded username and password final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", ""); //Decode username and password String usernameAndPassword = null; try { usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword)); } catch (Exception e) { requestContext.abortWith(SERVER_ERROR); return; } //Split username and password tokens final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":"); final String username = tokenizer.nextToken(); final String password = tokenizer.nextToken(); //Verifying Username and password if(!(username.equalsIgnoreCase("admin") && password.equalsIgnoreCase("password"))){ requestContext.abortWith(ACCESS_DENIED); return; } //Verify user access if(method.isAnnotationPresent(RolesAllowed.class)) { RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class); Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value())); //Is user valid? if( ! isUserAllowed(username, password, rolesSet)) { requestContext.abortWith(ACCESS_DENIED); return; } } } } private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet) { boolean isAllowed = false; //Step 1. Fetch password from database and match with password in argument //If both match then get the defined role for user from database and continue; else return isAllowed [false] //Access the database and do this part yourself //String userRole = userMgr.getUserRole(username); String userRole = "ADMIN"; //Step 2. Verify user role if(rolesSet.contains(userRole)) { isAllowed = true; } return isAllowed; } }</code></pre>   <h2>六、例子演示</h2> 运行这个项目作为Spring引导应用程序。现在测试rest资源。<br /> <strong>访问 GET /users resource<br /> <img alt="例子演示" class="img-thumbnail" src="/resources/assist/images/blog/9d1d46f7425a422b9cb44730e13285fd.png" /><br /> 使用POST方式并且没有认证信息去访问/users 资源接口</strong><br /> 可以看到返回的状态 status code 401.<br /> <img alt="401" class="img-thumbnail" src="/resources/assist/images/blog/90e9626483b4473fa79578ab2fa6b1e6.png" /><br /> <strong>使用POST方式并且添加认证信息去访问/users 资源接口</strong><br /> 使用<a href="http://www.leftso.com/tools/url.html" rel="external nofollow" target="_blank">此链接</a>生成base64编码的用户名和密码组合,以传递到授权头。<br /> <img alt="成功访问user" class="img-thumbnail" src="/resources/assist/images/blog/5cf84a5bba9c4178be76a13e5e2e7716.png" /><br />  
  • java编程spring security常用注解说明

    spring security常用注解@Secured、@PreAuthorize 、@PostAuthorize说明,Java编程,spring security首先是启用方法级别的权限控制<br /> <br /> @EnableGlobalMethodSecurity 开启Spring Security 全局方法安全<br /> @EnableGlobalMethodSecurity 可以配置多个参数<br /> prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..] <br /> secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用<br /> jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.<br /> <br /> 1.@Secured<br /> 功能:<br /> 支持单一角色或者多个角色之间的任何一个角色,不支持spring EL表达式<br /> <br /> 2.@PreAuthorize <br /> 注解适合进入方法前的权限验证, @PreAuthorize可以将登录用户的roles/permissions参数传到方法中。 <br /> 例子:<br />  @PreAuthorize("hasRole('ADMIN')")   拥有ADMIN角色权限才能访问<br />  @PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")  拥有ADMIN角色和DBA角色权限才能访问<br />  @PreAuthorize("hasAnyRole('ADMIN','DBA')") 拥有ADMIN或者DBA角色均可访问<br /> <br /> <br /> 3.@PostAuthorize<br /> 注解使用并不多,在方法执行后再进行权限验证。<br /> <br /> <br /> @PreAuthorize / @PostAuthorize 注解更适合方法级的安全,也支持Spring 表达式语言,提供了基于表达式的访问控制。
  • Windows 10 安装 IIS服务部署应用网站

    Windows 10 安装 IIS服务​IIS安装说明 步骤:1打开控制面板,依次找到:控制面板\程序\程序和功能如下图:​步骤:2将上面图片框的选项勾好,然后点击确定,等待几分钟即可Windows 10 安装 IIS服务​IIS安装说明 步骤:1打开控制面板,依次找到:控制面板\程序\程序和功能如下图:​步骤:2将上面图片框的选项勾好,然后点击确定,等待几分钟即可。步骤:3在浏览器中输入 http://localhost/看到下图内容即表示安装成功​IIS发布网站应用说明:在发布网站前请先将编译后的网站程序放到装有IIS服务的机器上 打开IIS管理器:打开“控制面板”在“管理工具”中打开“Internet Information Services(IIS)管理器”。“管理工具”中可能有两个IIS管理器,其中一个带有数字6.0,另一个不带,要打开不带数字的那个。​创建应用程序池:在IIS左侧树状列表的“应用程序池”上点击右键,然后选择“添加应用程序池”,填写“程序池名称”、“.NET CLR版本”及“托管管理模式”,点击“确定”后,创建应用程序池完成。“.NET CLR版本”请根据所开发网站的支持版本选择,一般用“.NET Framework 4.0”及以上版本开发的网站都要选“v4.0”,“.NET Framework 3.5”及以下的版本选“v2.0”,具体区别请自行百度。“托管管理模式”里面分“集成”和“经典”两种,“经典”模式是之前版本IIS里的,用新版本asp.net开发的网站大都为“集成”,具体区别请自行百度。 ​添加网站1:在IIS左侧树状列表中的“网站”上点击右键,选择“添加网站”。​添加网站2:在添加网站对话框中,输入网站名称,选择应用程序池,选择物理路径,即网站程序存放的位置,填写端口。注:以上信息请自行根据情况更改。​添加网站3:点击确定后即完成网站的发布,可在发布到网站名上右键-管理网站-浏览,查看网站是否可正常打开。
  • Spring AOP为何诞生

    Spring AOP来由,为何会出现Spring AOP这样的框架? 上一篇从Web开发演进过程的一个侧面简述了一下为什么会有Spring框架?事实上只介绍了为什么会有Spring IOC(控制反转/依赖注入)以及Spring IOC的雏形。我们知道Spring的两个核心知识点是:IOC和AOP。因此,这一篇还是以Web开发演进过程为线索继续探讨一下为什么会有Spring AOP?等介绍完这两个核心的知识点之后,才会进一步展开对Spring核心原理的探讨!<h2>引言</h2>     上一篇从Web开发演进过程的一个侧面简述了一下为<a rel="" target="_blank"href="http://www.leftso.com/blog/335.html" rel="" target="_blank">什么会有Spring框架?</a>事实上只介绍了为什么会有Spring IOC(控制反转/依赖注入)以及Spring IOC的雏形。我们知道Spring的两个核心知识点是:IOC和AOP。因此,这一篇还是以Web开发演进过程为线索继续探讨一下为什么会有Spring AOP?等介绍完这两个核心的知识点之后,才会进一步展开对Spring核心原理的探讨!<br />   <h2><strong>一、Web开发演进到一定阶段的痛点</strong></h2> <p>我们在初学习Java Web的时候,应该都经历了以下的阶段:</p> <p>(1)一个主函数main中包含了所有的方法; <br /> (2)将主函数中的方法进行拆分封装,抽取为一个个的方法; <br /> (3)按照每一个方法不同的功能分为一个个的类; <br /> (4)有了MVC模型之后,我们按照MVC的思想将我们的代码拆分为三层,每层负责不同的功能,进行分门别类的管理;</p> <p>很多程序的功能还可以通过继承关系而得到重用,进一步提高了开发效率。再后来,又出现了各种各样的设计模式,使设计程序功能变得得心应手。</p> <p>在面向对象的大环境下,我们可以很好地组织代码,通过继承、封装和多态的思想去设计一个个比较让人满意的类,但是我们慢慢的发现,我们的代码中逐渐多了很多重复性的代码,有人可能会想到,把这些重复性的代码抽取出来不就好了吗?是这样的,我们看一下这种思路的一个实例:<br /> <img alt="操作实例" class="img-thumbnail" src="/resources/assist/images/blog/77597820f9204bc8892a8c54a39aa026.png" /></p> <p>    可以看到,上述代码功能上确实可以实现,但是我们的业务代码已经被这些非核心的代码所混淆,并且占据了大量的空间!显然这种显示的调用过程成为了我们开发过程中的一个痛点,如何将类似这种的非核心的代码剥离出去成为一个迫切需要解决的问题!</p> <p>    不仅如此,假设我们要控制每一个方法的访问权限,只允许一部分用户进行访问,在不考虑过滤器的情况下,我们是不是需要在每一个方法开始的时候判断用户是否具有该权限,如果有的话就可以进行访问,如果没有的话,就不允许进行访问!</p> <p>    诸如此类,还有数据库事务的控制,数据库连接的创建和关闭等等,这些都充斥这大量重复性的模板代码!一个很现实的问题,假如有一天,业务需求不需要进行日志记录了,那岂不是我们需要把以前写的代码,全部删掉!想想都是一件很可怕的事情!<br /> <br />  </p> <h2><strong>二、使用设计模式进行一次改进</strong></h2> <p>    如果你对设计模式玩的比较熟的话,这个时候你可能会想到使用<strong>JDK动态代理设计模式</strong>(动态代理设计模式可以在原有的方法前后添加判断、选择或其他逻辑)对上述代码进行改进,(关于什么是JDK动态代理,这里不再详细赘述,有不懂的的可以查阅相关资料具体了解一下!)修改后的代码如下:<br /> <img alt="Spring Aop实例代码片段1" class="img-thumbnail" src="/resources/assist/images/blog/6345b45100d740f5bff96e5bb827c1a7.png" /><br /> 上述为代理类,红色框中圈出的表示以前业务中的模板代码,这里直接输出表示方法执行的过程,以前的UserServiceImpl修改为如下(直接用输出的方式表示方法执行了):<br /> <img alt="Spring Aop实例代码片段2" class="img-thumbnail" src="/resources/assist/images/blog/4992beb9fe9749bcabc1877a68217a37.png" /><br /> 测试代码如下:<br /> <img alt="Spring Aop实例代码片段3" class="img-thumbnail" src="/resources/assist/images/blog/c243721d002947199bae526adb3d78d5.png" /><br />  </p> <p>    上述的执行结果可以看出,每次调用一个方法的时候前后都会调用我们期望的代码,实现了我们期望的标准!</p> <p>    通过JDK动态代理的方式,让我们彻底的解放出来了!</p> <h2><strong>三、撕开披在AOP身上的一层薄纱</strong></h2> <p>    上述过程中,我们看到在<strong>动态代理的<code>invoke</code></strong>方法里边,我们相当于在原有方法的调用前后“<strong>植入</strong>”了我们的通用日志记录代码,如果你看到这一层的话,那么恭喜你!你已经领悟到了AOP思想最核心的东西了!</p> <p>    上述抽取公共代码其实就是AOP中<strong>横切</strong>的过程,代理对象中在方法调用前后“<strong>植入</strong>”自己写的通用日志记录代码其实就是AOP中<strong>织入</strong>的过程!这个织入的代码也就是<strong>横切逻辑</strong>,织入代码的过程其实就是在原有的方法前后<strong>增强</strong> 原方法的过程!总的来说,我们想解决我们开发中的痛点,然后就出现了一种技术,这种技术手段就是AOP。</p> <p>AOP书面表述如下:<br /> <br />     AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容(Spring核心之一),是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。<br />  </p> <h2><strong>四、AOP与Spring AOP的关系</strong></h2> <p>    AOP是一种思想,不同的厂商或企业可能有不同的实现方式,为了更好的应用AOP技术,技术专家们成立了AOP联盟来探讨AOP的标准化,AOP联盟定义的AOP体系结构把与AOP相关的概念大致分为由高到低、从使用到实现的三层关系,AOP联盟定义的AOP体系结构如下图:<br /> <img alt="AOP联盟定义的AOP结构图" class="img-thumbnail" src="/resources/assist/images/blog/0411549085a543a3b156bfff159b1a73.png" /><br />  </p> <p>    在AOP联盟定义的AOP体系结构下有很多的实现者,例如:AspectJ、AspectWerkz、JBoss AOP、Spring AOP等。Spring AOP就是在此标准下产生的,这里不再深入Spring AOP的其他概念,这些概念会在后期探讨。</p> <h2><strong>五、其他问题</strong></h2> <p>    上述通过动态代理的方式实现了简单的AOP,但是值得注意的是,我们的代理目标对象必须实现一个接口,要是一个接口的实现类,这是因为再生成Proxy对象的时候这个方法需要一个目标对象的接口:<br />  </p> <p><img alt="Spring Aop实例代码片段4" class="img-thumbnail" src="/resources/assist/images/blog/08942f7d864949a996120c9e0cde7037.png" /><br />  </p> <p>    显然,有些特殊的场景使用JDK动态代理技术的话,已经不能够满足我们的场景了,又遇到痛点了!凡事不劳我们操心的Spring框架已经替我们想到了,既然你有这种需求,我就使用一种技术帮你实现就行了,Spring在这里使用CGLib动态代理的方式实现了我们的这种诉求。</p> <p>CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势的织入横切逻辑。</p> <p>    看到这里,我们会想以后会不会还有CGLib解决不了得问题啊?我们已经很清楚的知道了对于Spring AOP来说,使用到了JDK动态代理技术和CGLib动态代理技术,这两种方式已经实现了我们绝大多数的场景,如果还有不能满足的需求,迫切需要解决的痛点,我相信Spring还会采用相应的技术来满足这些场景。</p> <h2><strong>六、总结</strong></h2> <p>    上述的过程,大致从一个侧面探讨了一下我们为什么需要AOP,AOP与Spring AOP的关系以及Spring AOP两种实现的方式(JDK动态代理和CGLib动态代理)。</p> <p>    Spring不尝试提供最为完善的AOP实现,它更侧重于提供一种和Spring IOC容器整个的AOP实现,用于解决实际的问题,在Spring中无缝的整合了Spring AOP、Spring IOC和AspectJ。</p> <p>    当然,Spring AOP的内容不仅仅有这些!例如:我们在使用Spring AOP的时候只是简单的配置了一下(通过XML或注解进行配置),没有像<code>ProxyDemo</code>测试类中的那样,还需要我们手动的调用<code>ProxyFactory</code> 来创建代理对象,然后调用我们的目标方法,其实Spring AOP在内部已经帮我们把这些事情做好了,具体的原理后期会继续探讨。另外,Spring如何整合Spring IOC和AOP的,这一点也会在后期探讨。</p> <p>    最后补充一下!动态代理或者设计模式重要吗?很重要!Spring AOP用到了动态代理,Spring事务管理用到了动态代理,MyBatis数据库连接池用到了动态代理,MyBatis创建Mapper用到了动态代理等等,你说重要不!要想踏进这些高层框架原理的大门,设计模式首先是我们的第一段台阶!</p> <p> </p>
  • zTree实现打开页面时异步加载数据及选中项ID提交到后台

    zTree实现打开页面时异步加载数据及选中项ID提交到后台1.页面 <pre> <code class="language-html"><!DOCTYPE html> <HTML> <HEAD> <TITLE> ZTREE DEMO </TITLE> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="demoStyle/demo.css" type="text/css"> <link rel="stylesheet" href="zTreeStyle/zTreeStyle.css" type="text/css"> <script type="text/javascript" src="ztreeJs/jquery-1.4.4.min.js"></script> <script type="text/javascript" src="ztreeJs/jquery.ztree.all.min.js"></script> </HEAD> <BODY> <div style="margin-left: 5%"> <article class="page-container"> <div class="row cl"> <label class="form-label col-xs-3 col-sm-3"><span class="c-red">*</span>角色名称:</label> <div class="formControls col-xs-6 col-sm-6"> <input type="hidden" name="roleId" value="111" id="roleId"/> <input type="text" class="input-text" value="测试" id="roleName" name="roleName"> </div> </div> <div class="row cl"> <label class="form-label col-xs-3 col-sm-3">权限列表:</label> <div class="formControls col-xs-6 col-sm-6"> <!-- 权限代码 zTree--> <div class="content_wrap"> <div class="zTreeDemoBackground left"> <ul id="treeDemo" class="ztree"></ul> </div> </div> </div> </div> <div class="row cl"> <div class="col-xs-3 col-sm-3 col-xs-offset-3 col-sm-offset-3"> <button type="button" class="btn btn-success radius sbm""> <i class="icon-ok"></i> 确定 </button> <button type="button" class="btn btn-success radius reset"> <i class="icon-ok"></i> 取消 </button> </div> </div> </article> </div> </BODY> </HTML></code></pre> <br /> 2.js <pre> <code class="language-java">//假设我们做的是为某个角色设置菜单权限,并且新增和修改权限用的一个页面 $(function() { var roleId=$("#roleId").val(); onLoadZTree(roleId); }); var setting = { check : { enable : true }, data : { simpleData : { enable : true, idKey : "id", pIdKey : "pId", rootPId : "" } } }; var treeNodes; function onLoadZTree(roleId) { $.ajax({ type:'post',//请求方式:post dataType : 'json',//数据传输格式:json url:'/sysRoleMenu/menuTreeAjax',//获取角色菜单URL地址 data:{"sysRoleId":roleId}, async:false,//是否异步 cache : false,//是否使用缓存 error:function(errorMsg){ //请求失败处理函数 alert('亲,请求失败!'); }, success:function(data){ if(data.errCode == 'ok'){ treeNodes = data.data;//把后台封装好的简单Json格式赋给treeNodes }else{ alert('亲,初始化角色菜单失败'); } } }); //这里我们假设返回的数据是: var treeNodes =[ { id:1, pId:0, name:"父节点1", open:true, checked:true}, { id:11, pId:1, name:"父节点11", checked:true}, { id:111, pId:11, name:"叶子节点111", checked:true}, { id:112, pId:11, name:"叶子节点112", checked:true}, { id:113, pId:11, name:"叶子节点113", checked:true}, { id:114, pId:11, name:"叶子节点114", checked:true}, { id:12, pId:1, name:"父节点12"}, { id:121, pId:12, name:"叶子节点121"}, { id:122, pId:12, name:"叶子节点122"}, { id:123, pId:12, name:"叶子节点123"}, { id:124, pId:12, name:"叶子节点124"}, { id:13, pId:1, name:"父节点13", isParent:true}, { id:2, pId:0, name:"父节点2"}, { id:21, pId:2, name:"父节点21", open:true}, { id:211, pId:21, name:"叶子节点211"}, { id:212, pId:21, name:"叶子节点212"}, { id:213, pId:21, name:"叶子节点213"}, { id:214, pId:21, name:"叶子节点214"}, { id:22, pId:2, name:"父节点22"}, { id:221, pId:22, name:"叶子节点221"}, { id:222, pId:22, name:"叶子节点222"}, { id:223, pId:22, name:"叶子节点223"}, { id:224, pId:22, name:"叶子节点224"}, { id:23, pId:2, name:"父节点23"}, { id:231, pId:23, name:"叶子节点231"}, { id:232, pId:23, name:"叶子节点232"}, { id:233, pId:23, name:"叶子节点233"}, { id:234, pId:23, name:"叶子节点234"}, { id:3, pId:0, name:"父节点3", isParent:true} ]; var t = $("#treeDemo"); t = $.fn.zTree.init(t, setting, treeNodes) }; //组装被选中的菜单为jsonArray格式 function onCheck() { var roleId = $("#roleId").val(); var roleName = $("#roleName").val(); var treeObj = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = treeObj.getCheckedNodes(true);//获取被选中的节点 var jsonArr = "["; for (var i = 0; i < nodes.length; i++) { jsonArr += "{"; jsonArr += "\"roleId\":\""+roleId+"\","; jsonArr += "\"roleName\":\""+roleName+"\","; jsonArr += "\"menuId\":\""+nodes[i].id+"\","; jsonArr += "\"menuName\":\""+nodes[i].name+"\""; jsonArr += "}" jsonArr += ',' } jsonArr = jsonArr.substring(0, jsonArr.length - 1); jsonArr += "]"; return jsonArr; }; $(function() { $(".sbm").click(function(){ var roleId= $("#roleId").val(); var sysRoleMenuStr = onCheck(); $.ajax({ type:'post',//请求方式:post dataType : 'json',//数据传输格式:json url:'/sysRoleMenu/createSysRoleMenu', data:{"sysRoleId":roleId,"sysRoleMenuStr":sysRoleMenuStr}, async:false,//是否异步 cache : false,//是否使用缓存 error:function(errorMsg){ alert("亲分配权限菜单成功"); //成功之后你需要做什么操作都在这里写 }, success:function(data){ if(data.errCode == 'ok'){ alert("亲分配权限菜单出错"); }else{ alert("亲分配权限菜单出错"); } } }); } }); }); </code></pre> 3.控制层 <pre> <code class="language-java">@Controller @RequestMapping(value = "/sysRoleMenu") public class SysRoleMenuController{ /** * 初始化菜单树列表 * * @param model * @param sysRoleId * @return */ @ResponseBody @RequestMapping("/menuTreeAjax") public BaseResult initMenuTreeList(Model model, String sysRoleId) { BaseResult baseResult = new BaseResult(); try { List<SysMenuTreeVo> MenuTreeList = sysRoleMenuService.getSysMenuTreeVo(sysRoleId); baseResult = new BaseResult(ResultCode.AJAX_OK, MenuTreeList, "数据加载成功"); } catch (Exception e) { baseResult = new BaseResult(ResultCode.AJAX_Err, "", e.getMessage()); return baseResult; } return baseResult; } /** * 创建角色菜单权限 * * @param model * @param sysRoleId * @param sysRoleMenuStr * @return */ @ResponseBody @RequestMapping("/createSysRoleMenu") public BaseResult createSysRoleMenu(Model model, String sysRoleId, String sysRoleMenuStr) { // 解析数据 List<SysRoleMenu> sysRoleMenuList = changeSysRoleMenuList(sysRoleMenuStr); BaseResult baseResult = new BaseResult(); try { sysRoleMenuService.createSysRoleMenu(sysRoleId, sysRoleMenuList);//这个方法我采用的是先删除角色下面的所有菜单权限,然后再执行的新增,这样新增修改就只用一个页面 baseResult = new BaseResult(ResultCode.AJAX_OK, "", "创建成功"); } catch (Exception e) { baseResult = new BaseResult(ResultCode.AJAX_Err, "", e.getMessage()); log.error("SysRoleMenuController类createSysRoleMenu方法,创建菜单权限出错!", e); return baseResult; } return baseResult; } /** * 解析菜单jsonArray数据 * * @param sysRoleMenuStr * @return * @throws LoadRecordException */ public List<SysRoleMenu> changeSysRoleMenuList(String sysRoleMenuStr) throws LoadRecordException { List<SysRoleMenu> sysRoleMenus = null; if (StringUtil.isBlank(sysRoleMenuStr)) { return sysRoleMenus; } try { sysRoleMenus = new ArrayList<SysRoleMenu>(); JSONArray jsonArray = JSON.parseArray(sysRoleMenuStr); for (Object object : jsonArray) { SysRoleMenu sysRoleMenu = new SysRoleMenu(); JSONObject jsonObject = JSON.parseObject(object.toString()); String roleId = jsonObject.getString("roleId"); String roleName = jsonObject.getString("roleName"); String menuId = jsonObject.getString("menuId"); String menuName = jsonObject.getString("menuName"); sysRoleMenu.setRoleId(roleId); sysRoleMenu.setRoleName(roleName); sysRoleMenu.setMenuId(menuId); sysRoleMenu.setMenuName(menuName); sysRoleMenus.add(sysRoleMenu); } } catch (Exception e) { log.error("解析菜单数据出错,class:SysRoleMenuController;method:changeProductSaleRegionList;exception:Exception", e); throw new LoadRecordException("解析菜单数据错误,请确定你的格式:" + e.getMessage(), e); } return sysRoleMenus; } }</code></pre> 4.前台需要的封装对象 <pre> <code class="language-java">public class SysMenuTreeVo { // 菜单主键ID private String id; // 上级菜单 private String pId; // 菜单名称 private String name; // 是否展开 private Boolean open = Boolean.FALSE; // 是否选中 private Boolean checked = Boolean.FALSE; public SysMenuTreeVo() { } public SysMenuTreeVo(SysMenu sysMenu, Boolean checked, Boolean open) { if (sysMenu != null) { this.id = sysMenu.getId(); if (StringUtil.isNotBlank(sysMenu.getParentId())) { this.pId = sysMenu.getParentId(); } else { this.pId = "0"; } this.name = sysMenu.getName(); } this.open = open; this.checked = checked; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getpId() { return pId; } public void setpId(String pId) { this.pId = pId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Boolean getOpen() { return open; } public void setOpen(Boolean open) { this.open = open; } public Boolean getChecked() { return checked; } public void setChecked(Boolean checked) { this.checked = checked; } @Override public String toString() { return "SysMenuTreeVo [id=" + id + ", pId=" + pId + ", name=" + name + ", open=" + open + ", checked=" + checked + "]"; }</code></pre> 5.页面展示效果<br /> <img alt="" class="img-thumbnail" src="/resources/assist/images/blog/8601236fd58f40269b72ca344c8008bd.png" /><br /> <br />  
  • centos6.8 yum安装和配置ftp server(vsftpd)客服端

    centos6.8 yum安装和配置ftp server(vsftpd)客服端以及ftp常见问题解决,vsftpd################################ FTP Server ##########################<br /> centos6.8 yum安装和配置ftp server(vsftpd)<br /> 安装前提<br /> <strong>1.root用户<br /> 2.主机联网</strong><br /> 安装命令: <pre> <code class="language-bash"> yum install vsftpd</code></pre> <br /> 安装过程:<br /> <br /> <img alt="安装过程" class="img-thumbnail" src="/resources/assist/images/blog/2a19add45c0746c59ced57324b655948.png" /><br /> <br /> 查看是否安装成功<br />   <pre> <code>[root@VM_xx_xx_centos ~]# rpm -qa | grep vsftpd vsftpd-2.2.2-21.el6.x86_64</code></pre> <br /> 如果查询出来有vsftpd-版本号说明已经安装成功。<br /> <br /> 查看yum安装的配置信息: <pre> <code>[root@VM_xx_xx_centos ~]# whereis vsftpd vsftpd: /usr/sbin/vsftpd /etc/vsftpd /usr/share/man/man8/vsftpd.8.gz</code></pre> <br /> 安装的配置目录基本这里就三个: <pre> <code>/usr/sbin/vsftpd /etc/vsftpd ​​​​​​​/usr/share/man/man8/vsftpd.8.gz</code></pre> <br /> 很明显,核显配置文件sftpd.conf在/etc/vsftpd中<br /> 切换目录,修改配置.<br /> <span style="color:#e74c3c">注意修改前最好先备份一个原有的,纯属个人习惯</span><br /> sftp.conf常用配置信息说明 <pre> <code> # 允许本地用户登录 local_enable=YES # 本地用户的写权限 write_enable=YES # 使用FTP的本地文件权限,默认为077 # 一般设置为022 local_umask=022 # 切换目录时 # 是否显示目录下.message的内容 dirmessage_enable=YES dirlist_enable = NO #验证方式 #pam_service_name=vsftpd # 启用FTP数据端口的数据连接 connect_from_port_20=YES # 以独立的FTP服务运行 listen=yes # 修改连接端口 #listen_port=2121 ######### 匿名登录设置 ########### # 允许匿名登录 anonymous_enable=NO # 如果允许匿名登录 # 是否开启匿名上传权限 #anon_upload_enable=YES # 如果允许匿名登录 # 是否允许匿名建立文件夹并在文件夹内上传文件 #anon_mkdir_write_enable=YES # 如果允许匿名登录 # 匿名帐号可以有删除的权限 #anon_other_write_enable=yes # 如果允许匿名登录 # 匿名的下载权限 # 匿名为Other,可设置目录/文件属性控制 #anon_world_readable_only=no # 如果允许匿名登录 # 限制匿名用户传输速率,单位bite #anon_max_rate=30000 ######### 用户限制设置 ########### #### 限制登录 # 用userlist来限制用户访问 #userlist_enable=yes # 名单中的人不允许访问 #userlist_deny=no # 限制名单文件放置的路径 #userlist_file=/etc/vsftpd/userlist_deny.chroot #### 限制目录 # 限制所有用户都在家目录 #chroot_local_user=yes # 调用限制在家目录的用户名单 chroot_list_enable=YES # 限制在家目录的用户名单所在路径 chroot_list_file=/etc/vsftpd/chroot_list ######### 日志设置 ########### # 日志文件路径设置 xferlog_file=/var/log/vsftpd.log # 激活上传/下载的日志 xferlog_enable=YES # 使用标准的日志格式 #xferlog_std_format=YES ######### 安全设置 ########### # 用户空闲超时,单位秒 #idle_session_timeout=600 # 数据连接空闲超时,单位秒 #data_connection_timeout=120 # 将客户端空闲1分钟后断开 #accept_timeout=60 # 中断1分钟后重新连接 #connect_timeout=60 # 本地用户传输速率,单位bite #local_max_rate=50000 # FTP的最大连接数 #max_clients=200 # 每IP的最大连接数 #max_per_ip=5 ######### 被动模式设置 ########### # 是否开户被动模式 pasv_enable=yes # 被动模式最小端口 pasv_min_port=5000 # 被动模式最大端口 pasv_max_port=6000 ######### 其他设置 ########### # 欢迎信息 ftpd_banner=Welcome to Ftp Server!</code></pre> <br /> 添加ftp防火墙规则,一般用于安全策略 <pre> <code> /sbin/iptables -I INPUT -p tcp --dport 21 -j ACCEPT /etc/rc.d/init.d/iptables save /etc/init.d/iptables restart</code></pre> <br /> 常用启动重启停止ftp命令: <pre> <code>#启动 service vsftpd start #停止 service vsftpd stop #重启 service vsftpd restart #查看运行状态 service vsftpd status</code></pre> <br /> ftp server 分两种模式<br /> 主动模式配置 <pre> <code>#主动模式 #开启主动模式 port_enable=YES #当主动模式开启的时候 是否启用默认的20端口监听 connect_from_port_20=YES #上一选项使用NO参数是 指定数据传输端口 ftp_date_port=%portnumber%</code></pre> 被动模式配置 <pre> <code>#开启被动模式 pasv_enable=YES #被动模式最低端口 pasv_min_port=5100 #被动模式最高端口 pasv_max_port=5200</code></pre> <br /> 推荐使用被动模式<br /> <br /> <span style="color:#ff0000"><strong>注意:配置文件配置内容后面不允许有空格,所以最好注释写上面配置内容后无任何东西,否则会莫名奇妙报错</strong></span><br /> <br /> ################################ FTP Client ##########################<br /> 安装命令 <pre> <code>yum install ftp</code></pre> <br /> 安装过程<br /> <img alt="client" class="img-thumbnail" src="/resources/assist/images/blog/f54420cbd14f477282a1ecb09260402a.jpg" /><br /> <br /> 安装完成后执行ftp就可以进入ftp客服端<br /> <img alt="client" class="img-thumbnail" src="/resources/assist/images/blog/22b4ae19a1af4f62aec556baa0d514e7.jpg" /><br /> <br /> ################################ 常见问题 QA ################################<br /> 227 Entering Passive Mode<br /> 200 PORT command successful. Consider using PASV.<br /> 解决:<br /> 服务端配置文件中添加或修改以下内容,核心内容是最后一个pasv_address该参数必须绑定<br /> #sv_enable=YES<br /> #被动模式最低端口<br /> pasv_min_port=3100<br /> #被动模式最高端口<br /> pasv_max_port=3200<br /> <strong><span style="color:#cc0000">pasv_address=123.206.71.187(必须项,你自己主机的IP地址)</span></strong><br />   <p>227 Entering Passive Mode<br /> 解决:</p> <pre> <code>ftp> passive Passive mode on.</code></pre> <br /> <p> </p>
  • css如何控制英语单词中文拼音正常换行

    如何使html中的英语单词换行在css中有有一下几种换行策略:<br /> <br /> 1.<strong><em> word-break:break-all</em></strong>;只对英文起作用,以字母作为换行依据(既,如果一个单词在换行时比较长会自动拆分单词为两段,一段在本行,一段在下一行) <p>2. <em><strong>word-wrap:break-word</strong></em>; 只对英文起作用,以单词作为换行依据(如果单词最末出不够放,会自动将整个单词放到下一行)</p> <p>3.<em><strong>white-space:pre-wrap</strong></em>; 只对中文起作用,强制换行</p> <p>4.<em><strong>white-space:nowrap;</strong></em> 强制不换行,都起作用</p> <p>5.<em><strong>white-space:nowrap; overflow:hidden; text-overflow:ellipsis;</strong></em>不换行,超出部分隐藏且以省略号形式出现(本站首页有使用)</p> 参考文档说明: <p>注意,一定要指定容器的宽度,不然的话是没有用的。</p> <p>注意word-break 是IE5+专有属性</p> <p>语法:</p> <p>word-break : normal | break-all | keep-all</p> <p>参数:</p> <p>normal : 依照亚洲语言和非亚洲语言的文本规则,允许在字内换行</p> <p>break-all : 该行为与亚洲语言的normal相同。也允许非亚洲语言文本行的任意字内断开。该值适合包含一些非亚洲文本的亚洲文本</p> <p>keep-all : 与所有非亚洲语言的normal相同。对于中文,韩文,日文,不允许字断开。适合包含少量亚洲文本的非亚洲文本</p>