搜索词>>Code Review 耗时0.0070
  • Java Code Review

    目标和原则提高代码质量,及早发现潜在缺陷,降低修改/弥补缺陷的成本促进团队内部知识共享,提高团队整体水平评审过程对于评审人员来说,也是一种思路重构的过程,帮助更多的人理解系统是一个传递知识的手段,可以让其它并不熟悉代码的人知道作者的意图和想目标和原则提高代码质量,及早发现潜在缺陷,降低修改/弥补缺陷的成本促进团队内部知识共享,提高团队整体水平评审过程对于评审人员来说,也是一种思路重构的过程,帮助更多的人理解系统是一个传递知识的手段,可以让其它并不熟悉代码的人知道作者的意图和想法,从而可以在以后轻松维护代码可以被用来确认自己的设计和实现是一个清楚和简单的鼓励相互学习对方的长处和优点高效迅速完成Code Review注意事项经常进行Code Review(1)要Review的代码越多,那么要重构,重写的代码就会越多。而越不被程序作者接受的建议也会越多,唾沫口水战也会越多。 (2)程序员代码写得时候越长,程序员就会在代码中加入越来越多的个人的东西。 (3)越接近软件发布的最终期限,代码也就不能改得太多。Code Review不要太正式,而且要短忘了那个代码评审的Checklist吧,走到你的同事座位跟前,像请师父一样请他坐到你的电脑面前,然后,花5分钟给他讲讲你的代码,给他另外一个5分钟让他给你的代码提提意见,这比什么都好。而如果你用了一个Checklist,让这个事情表现得很正式的话,下面两件事中必有一件事会发生: (1)只有在Checklist上存在的东西才会被Review。 (2)Code Reviews 变成了一种礼节性的东西,你的同事会装做很关心你的代码,但其实他心里想着尽快地离开你。 只有不正式的Code Review才会让你和评审者放轻松,人只有放松了,才会表现得很真实,很真诚。记住Review只不过是一种形式,而只有在相互信任中通过相互的讨论得到了有意义和有建设性的建议和意见,那才是最实在的。不然,作者和评审者的关系就会变成小偷和警察的关系。尽可能的让不同的人Review你的代码如果可能的话,不要总是只找一个人来Review你的代码,不同的人有不同的思考方式,有不同的见解,所以,不同的人可以全面的从各个方面评论你的代码。 但不要太多了,人多嘴杂反而适得其反,基本上来说,不要超过3个人,这是因为,这是一个可以围在一起讨论的最大人员尺寸。下面是几个优点: (1)从不同的方向评审代码总是好的。 (2)会有更多的人帮你在日后维护你的代码。 (3)这也是一个增加团队凝聚力的方法。保持积极的正面的态度程序员最大的问题就是“自负”,尤其当我们Review别人的代码的时候,我已经见过无数的场面,程序员在Code Review的时候,开始抨击别人的代码,质疑别人的能力。太可笑了,我分析了一下,这类的程序员其实并没有什么本事,因为他们指责对方的目的是想告诉大家自己有多么的牛,靠这种手段来表现自己的程序员,其实是就是传说中所说的“半瓶水”。所以,无论是代码作者,还是评审者,都需要一种积极向上的正面的态度,作者需要能够虚心接受别人的建议,因为别人的建议是为了让你做得更好;评审者也需要以一种积极的正面的态度向作者提意见,因为那是和你在一个战壕里的战友。记住,你不是一段代码,你是一个人!学会享受Code Review这可能是最重要的一个提示了,如果你到了一个人人都喜欢Code Review的团,那么,你会进入到一个生机勃勃的地方,在那里,每个人都能写出质量非常好的代码,在那里,你不需要经理的管理,团队会自适应一切变化,他们相互学习,相互帮助,不仅仅是写出好的代码,而且团队和其中的每个人都会自动进化,最关键的是,这个是一个团队。 ​​​​​​​
  • eclipse如何关闭java代码中某些部分/片段代码不被格式化

    eclipse如何关闭java代码中某些部分/片段代码不被格式化,eclipse,代码部分不被格式化,How to turn off the Eclipse code formatter for certain sections of Java code?eclipse如何关闭java代码中某些部分/片段代码不被格式化,eclipse,代码部分不被格式化,<br /> How to turn off the Eclipse code formatter for certain sections of Java code?<br /> <br /> 1.设置eclipse开启Off/On tags<br /> <br /> 依次点击菜单<br /> preferences->Java->Code Style->Formatter<br /> 点击Edit<br /> <img alt="1" class="img-thumbnail" src="/assist/images/blog/d2fc6ee509064f309342ae36d146bb42.png" /><br /> <br /> 找到tag,点击enable Off/On tags<br /> <img alt="2" class="img-thumbnail" src="/assist/images/blog/44a828ea10834828a6587602e9e42c03.png" /><br /> <br /> <br /> 在不想被格式化的代码之间添加以下注释标签即可<br />   <pre> <code class="language-java">import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import org.springframework.util.Base64Utils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSONObject; import com.cqax.axschoolweb2.common.pojo.Result; /** * 验证码公用处理类 * * @author xq * */ @RestController public class ValidationCodeController { //@formatter:off /** * * @api {get} /apis/validation/getcode.json 获取验证码 * * @apiName GetValidateCode * * @apiGroup Validation * * @apiParam {String} [timeStamp] 时间戳 * * @apiSuccessExample {String} 返回值: * { "id": "1be34f4a324c4123b0325ecf0593d70e", "data": { "image": "iVBORwCCAAAAAAAAAAAAAAAAA==" }, "code": "00000", "dateTime": "2017-05-23 14:00:23", "msg": null } * */ //@formatter:on @GetMapping("/apis/validation/getcode.json") public Result getCode() { try { InputStream is = new FileInputStream(new File("e:/2.png")); ByteArrayOutputStream bao = new ByteArrayOutputStream(); byte[] cache = new byte[4096]; while (is.read(cache) != -1) { bao.write(cache); } String rs = Base64Utils.encodeToString(bao.toByteArray()); JSONObject obj = new JSONObject(); obj.put("image", rs); is.close(); return Result.success(obj); } catch (Exception e) { return Result.fail("eeeee", "错误,异常," + e.getMessage()); } } //@formatter:off /** * @api {get} /apis/validation/compare.json 对比验证码 * * @apiName CompareValidateCode * * @apiGroup Validation * * @apiParam {String} vcode 验证码 * * @apiSuccessExample {json} 返回值: * { "id":"f47611a1a7ee4d4c8c0420a4f7e4b228", "data":{ "result":true }, "code":"00000", "dateTime":"2017-05-23 14:41:36", "msg":null } */ //@formatter:on @GetMapping("/apis/validation/compare.json") public Result compare(String vcode) { JSONObject obj = new JSONObject(); obj.put("result", true);// 对比结果 return Result.success(obj); } public Result getPhoneCode(){ JSONObject obj=new JSONObject(); return null; } } </code></pre> <br />
  • svn: E165001: Commit blocked by pre-commit hook

    Java编程中,svn提交代码报错svn: E165001: Commit blocked by pre-commit hook (exit code 1) with output:解决方法Java编程中,svn提交代码报错<br /> svn: E165001: Commit blocked by pre-commit hook (exit code 1) with output:<br /> <br /> 导致问题的原因<br /> comment内容太少<br /> <br /> <br /> 解决办法:comment内容多写点儿。就是提交svn的备注内容写多点儿就搞定。
  • Spring Boot Security 数据库方式入门案例

    spring boot 整合spring security采用mongodb数据库方式<p>案例中有使用mongodb数据库,具体的spring boot整合mongodb的案例参考地址<a href="http://www.leftso.com/blog/135.html" rel="nofollow" target="_blank">http://www.leftso.com/blog/135.html</a><br /> <br /> <a href="http://projects.spring.io/spring-security/" rel="nofollow" target="_blank">Spring Security</a>有一些使用复杂的意见。那当然,当你看的时候,它的范围很复杂,因为它的范围涵盖了大量的用例。事实上,真正的spring的精神,你不必一次使用你所拥有的用例的一切功能。事实上,当你开始使用<a href="http://projects.spring.io/spring-boot/" rel="nofollow" target="_blank">Spring Boot</a>进行樱桃挑选并将其恢复时,它似乎并不复杂。</p> <p>我们从使用情况开始,我在想一些比较常见的东西,几乎每个项目都出现一些基本的访问限制。所以,这样一个应用程序的要求可以是:</p> <ul> <li>该应用将有用户,每个用户角色为管理员或用户</li> <li>他们通过电子邮件和密码登录</li> <li>非管理员用户可以查看他们的信息,但不能窥视其他用户</li> <li>管理员用户可以列出并查看所有用户,并创建新的用户</li> <li>定制表单登录</li> <li>“记住我”验证懒惰</li> <li>注销的可能性</li> <li>主页将提供给所有人,不经过验证</li> </ul> <p>这样的<a href="https://github.com/bkielczewski/example-spring-boot-security" rel="nofollow" target="_blank">应用程序</a>的<a href="https://github.com/bkielczewski/example-spring-boot-security" rel="nofollow" target="_blank">源代码在GitHub上</a>,供您查看。建议您将源代码打开,以下将是某些关键点的评论。啊,和安全相关的东西接近尾声,所以只要知道基础知识就可以向下滚动。</p> <h4>依赖关系</h4> <p>此外,标准的Spring Boot依赖关系,最重要的依赖关系是Spring Security,Spring Data JPA的启动器模块,因为我们需要某处存储用户,而嵌入式内存中的HSQLDB作为存储引擎。</p> <pre> <code class="language-xml"><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> </dependency> </code></pre> <p>在现实生活中,您可能会更有兴趣连接到某种外部数据库引擎,例如<a href="http://kielczewski.eu/2014/05/database-connection-pooling-with-bonecp-in-spring-boot-application/" rel="nofollow" target="_blank">本文中关于使用BoneCP的数据库连接池的描述</a>。我也使用<a href="http://freemarker.org/" rel="nofollow" target="_blank">Freemarker</a>作为模板引擎,但是如果这不是你的事情,那么对于JSP来说,重写它也应该很简单,就像在<a href="http://kielczewski.eu/2014/04/spring-boot-mvc-application/" rel="nofollow" target="_blank">本文中关于Spring MVC应用程序一样</a>。</p> <h4>域模型</h4> <p>所以我们会有这样的<code>User</code>实体:</p> <pre> <code class="language-java">@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false, updatable = false) private Long id; @Column(name = "email", nullable = false, unique = true) private String email; @Column(name = "password_hash", nullable = false) private String passwordHash; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) private Role role; // getters, setters } </code></pre> <p>如您所见,只有密码的哈希将存储在数据库中,这通常是一个好主意。该<code>email</code>领域还有一个独特的约束,但它不是主要的关键。由于<code>id</code>某个原因,电子邮件地址是您不想在访问日志中显示的相当敏感的信息,因此用户被标识,所以我们将尽可能地使用id。</p> <p><code>Role</code> 是一个简单的枚举:</p> <pre> <code class="language-java">public enum Role { USER, ADMIN } </code></pre> <p>除此之外,创建新用户的表单将是很好的:</p> <pre> <code class="language-java">public class UserCreateForm { @NotEmpty private String email = ""; @NotEmpty private String password = ""; @NotEmpty private String passwordRepeated = ""; @NotNull private Role role = Role.USER; } </code></pre> <p>这将用作Web层和服务层之间的数据传输对象(DTO)。它由Hibernate Validator验证约束注释,并设置一些合理的默认值。请注意,它与<code>User</code>对象略有不同,因此我希望将<code>User</code>实体“泄漏” 到Web层中,我真的不能。</p> <p>这就是我们现在所需要的。</p> <h4>服务层</h4> <p>在服务层,业务逻辑应该是什么,我们需要一些东西来检索<code>User</code>他的id,电子邮件,列出所有的用户并创建一个新的用户。</p> <p>所以接口将是:</p> <pre> <code class="language-java">public interface UserService { Optional<User> getUserById(long id); Optional<User> getUserByEmail(String email); Collection<User> getAllUsers(); User create(UserCreateForm form); } </code></pre> <p>服务的实现:</p> <pre> <code class="language-java">@Service public class UserServiceImpl implements UserService { private final UserRepository userRepository; @Autowired public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Override public Optional<User> getUserById(long id) { return Optional.ofNullable(userRepository.findOne(id)); } @Override public Optional<User> getUserByEmail(String email) { return userRepository.findOneByEmail(email); } @Override public Collection<User> getAllUsers() { return userRepository.findAll(new Sort("email")); } @Override public User create(UserCreateForm form) { User user = new User(); user.setEmail(form.getEmail()); user.setPasswordHash(new BCryptPasswordEncoder().encode(form.getPassword())); user.setRole(form.getRole()); return userRepository.save(user); } } </code></pre> <p>这里不值得一个评论 - 服务代理到<code>UserRepository</code>大部分时间。值得注意的是,在该<code>create()</code>方法中,该表单用于构建一个新<code>User</code>对象。哈希是从使用的密码<code>BCryptPasswordEncoder</code>生成的,这应该比臭名昭着的MD5产生更好的哈希。</p> <p>的<code>UserRepository</code>定义如下:</p> <pre> <code class="language-java">public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findOneByEmail(String email); } </code></pre> <p>这里只添加一个非默认方法<code>findOneByEmail</code>。请注意,我希望它返回<code>User</code>包装在JDK8中<code>Optional</code>,这是Spring的一个新功能,并且使处理<code>null</code>值更容易。</p> <h4>Web层</h4> <p>这是控制者及其观点。为了满足应用的要求,我们至少需要一对夫妇。</p> <h4>主页</h4> <p>我们将处理<code>/</code>网站的根<code>HomeController</code>,其中只返回一个<code>home</code>视图:</p> <pre> <code class="language-java">@Controller public class HomeController { @RequestMapping("/") public String getHomePage() { return "home"; } } </code></pre> <h4>用户列表</h4> <p>同样,用户的列表将被映射到<code>/users</code>并由其处理<code>UsersController</code>。它<code>UserService</code>注入,要求它返回<code>Collection</code>的<code>User</code>对象,将它们放入了<code>users</code>模型属性,然后调用<code>users</code>视图名称:</p> <pre> <code class="language-java">@Controller public class UsersController { private final UserService userService; @Autowired public UsersController(UserService userService) { this.userService = userService; } @RequestMapping("/users") public ModelAndView getUsersPage() { return new ModelAndView("users", "users", userService.getAllUsers()); } } </code></pre> <h4>查看和创建用户</h4> <p>接下来,我们需要一个控制器来处理查看和创建一个新的用户,我称之为它<code>UserController</code>,它更复杂:</p> <pre> <code class="language-java">@Controller public class UserController { private final UserService userService; private final UserCreateFormValidator userCreateFormValidator; @Autowired public UserController(UserService userService, UserCreateFormValidator userCreateFormValidator) { this.userService = userService; this.userCreateFormValidator = userCreateFormValidator; } @InitBinder("form") public void initBinder(WebDataBinder binder) { binder.addValidators(userCreateFormValidator); } @RequestMapping("/user/{id}") public ModelAndView getUserPage(@PathVariable Long id) { return new ModelAndView("user", "user", userService.getUserById(id) .orElseThrow(() -> new NoSuchElementException(String.format("User=%s not found", id)))); } @RequestMapping(value = "/user/create", method = RequestMethod.GET) public ModelAndView getUserCreatePage() { return new ModelAndView("user_create", "form", new UserCreateForm()); } @RequestMapping(value = "/user/create", method = RequestMethod.POST) public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return "user_create"; } try { userService.create(form); } catch (DataIntegrityViolationException e) { bindingResult.reject("email.exists", "Email already exists"); return "user_create"; } return "redirect:/users"; } } </code></pre> <p>视图被映射到<code>/user/{id}</code>URL,由<code>getUserPage()</code>方法处理。它要求<code>UserService</code>一个用户<code>id</code>,它是从URL中提取的,并作为参数传递给此方法。你记得,<code>UserService.getUserById()</code>返回一个<code>User</code>包装的实例<code>Optional</code>。因此,<code>.orElseThrow()</code>被要求<code>Optional</code>获得一个<code>User</code> 实例,或者在它的时候抛出异常<code>null</code>。</p> <p>创建一个新<code>User</code>的映射到<code>/user/create</code>并由两种方法处理:<code>getUserCreatePage()</code>和<code>handleUserCreateForm()</code>。第一个只返回一个<code>user_create</code>具有空格式的视图作为<code>form</code>模型的属性。另一个响应<code>POST</code>请求,并<code>UserCreateForm</code>作为参数进行验证。如果表单中有错误,则由<code>BindingResult</code>该视图返回。如果表单确定,则将其传递给<code>UserService.create()</code>方法。</p> <p>还有一个额外的检查<code>DataIntegrityViolationException</code>。如果发生这种情况,则认为是因为尝试<code>User</code>使用数据库中已经存在的电子邮件地址来创建,因此再次呈现该表单。</p> <p>在现实生活中,知道哪个约束被严重违反是非常困难的(或者与ORM无关),所以当做这些假设时,至少应该记录异常情况以便进一步检查。应该注意防止这种异常发生在第一位,如对于重复的电子邮件的形式的正确验证。</p> <p>否则,如果一切正常,则重定向到<code>/users</code>URL。</p> <h3>定制验证 <code>UserCreateForm</code></h3> <p>该<code>@InitBinder</code>在注解的方法<code>UserController</code>添加<code>UserCreateFormValidator</code>到<code>form</code>参数,告诉它应该由它来验证。这样做的原因在于,<code>UserCreateForm</code>需要对两种可能的情况进行整体验证:</p> <ul> <li>检查密码和重复密码是否匹配</li> <li>检查电子邮件是否存在于数据库中</li> </ul> <p>这样做,<code>UserCreateFormValidator</code>实现如下:</p> <pre> <code class="language-java">@Component public class UserCreateFormValidator implements Validator { private final UserService userService; @Autowired public UserCreateFormValidator(UserService userService) { this.userService = userService; } @Override public boolean supports(Class<?> clazz) { return clazz.equals(UserCreateForm.class); } @Override public void validate(Object target, Errors errors) { UserCreateForm form = (UserCreateForm) target; validatePasswords(errors, form); validateEmail(errors, form); } private void validatePasswords(Errors errors, UserCreateForm form) { if (!form.getPassword().equals(form.getPasswordRepeated())) { errors.reject("password.no_match", "Passwords do not match"); } } private void validateEmail(Errors errors, UserCreateForm form) { if (userService.getUserByEmail(form.getEmail()).isPresent()) { errors.reject("email.exists", "User with this email already exists"); } } } </code></pre> <h4>在登录</h4> <p>它将映射到<code>/login</code>URL并处理<code>LoginController</code>:</p> <pre> <code class="language-java">@Controller public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public ModelAndView getLoginPage(@RequestParam Optional<String> error) { return new ModelAndView("login", "error", error); } } </code></pre> <p>请注意,它只处理<code>GET</code>请求方法,通过<code>error</code>在模型中返回具有可选参数的视图。<code>POST</code>表单的部分和实际处理将由Spring Security完成。</p> <p>表单的模板如下所示:</p> <pre> <code class="language-html"><form role="form" action="/login" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <div> <label for="email">Email address</label> <input type="email" name="email" id="email" required autofocus> </div> <div> <label for="password">Password</label> <input type="password" name="password" id="password" required> </div> <div> <label for="remember-me">Remember me</label> <input type="checkbox" name="remember-me" id="remember-me"> </div> <button type="submit">Sign in</button> </form> <#if error.isPresent()> <p>The email or password you have entered is invalid, try again.</p> </#if> </code></pre> <p>它具有普通<code>email</code>,<code>password</code>输入字段,一个<code>remember-me</code>复选框和一个提交按钮。如果出现错误,将显示说明认证失败的消息。</p> <p>这有效地包含了该应用程序功能所需的一切。剩下的是添加一些安全功能。</p> <h4>CSRF保护</h4> <p>关于<code>_csrf</code>上面窗体视图中出现的内容。它是由应用程序生成的用于验证请求的CSRF令牌。这是为了确保表单数据来自您的应用程序,而不是来自其他地方。这是Spring Security的一个功能,默认情况下由Spring Boot启动。</p> <p>对于JSP和Freemarker,<code>_csrf</code>变量只是暴露在视图中。它包含一个<code>CsrfToken</code>对象,该对象具有一个<code>getParameterName()</code>方法来获取一个CSRF参数名称(默认情况下是这个名称<code>_csrf</code>)和一个<code>getToken()</code>获取实际令牌的方法。然后,您可以将其放在一个隐藏的领域以及其余的表单中:</p> <pre> <code class="language-html"><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </code></pre> <h4>认证</h4> <p>现在我们有一个应用程序准备好了,是时候设置身份验证。当我们使用我们的登录表单识别那个人,作为现有用户,并且获得足够的信息以授权他们的进一步请求,认证意味着这一部分。</p> <h4>组态</h4> <p>需要添加Spring Security的此配置:</p> <pre> <code class="language-java">@Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .formLogin() .loginPage("/login") .failureUrl("/login?error") .usernameParameter("email") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/") .permitAll(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService) .passwordEncoder(new BCryptPasswordEncoder()); } } </code></pre> <p>这肯定需要一个解释。</p> <p>首先要提到的是<code>@Order</code>注释,它基本上保留了Spring Boot设置的所有默认值,只是在这个文件中覆盖它们。</p> <p>该<code>configure(HttpSecurity http)</code>方法是设置实际的基于URL的安全性。我们来看看它在这里做什么</p> <ul> <li>登录表格在下<code>/login</code>,允许所有。在登录失败时,<code>/login?error</code>会发生重定向。我们<code>LoginController</code>映射了这个URL。</li> <li>以登录形式保存用户名的参数称为“电子邮件”,因为这是我们用作用户名。</li> <li>注销URL是<code>/logout</code>允许的。之后,用户将被重定向到<code>/</code>。这里一个重要的意见是,如果CSRF保护开启,请求<code>/logout</code>应该是<code>POST</code>。</li> </ul> <p>该<code>configure(AuthenticationManagerBuilder auth)</code>方法是设置认证机制的地方。它的设置使得认证将被处理<code>UserDetailsService</code>,哪个实现被注入,并且密码预期被加密<code>BCryptPasswordEncoder</code>。</p> <p>值得一提的是还有很多其他的认证方法<code>UserDetailsService</code>。这只是一种使我们能够使用现有的服务层对象来实现的方法,因此它很适合这个应用。</p> <h3>UserDetailsS​​ervice</h3> <p>这<code>UserDetailsService</code>是Spring Security使用的接口,用于了解用户是否使用登录表单,他们的密码应该是什么,以及系统中有哪些权限。它有一个单一的方法:</p> <pre> <code class="language-java">public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; } </code></pre> <p>如果用户名存在,则该方法<code>loadUserByUsername()</code>返回<code>UserDetails</code>实例,如果不存在则返回实例<code>UsernameNotFoundException</code>。</p> <p>我的实现,注入到同一个<code>SecurityConfig</code>如下:</p> <pre> <code class="language-java">@Service public class CurrentUserDetailsService implements UserDetailsService { private final UserService userService; @Autowired public CurrentUserDetailsService(UserService userService) { this.userService = userService; } @Override public CurrentUser loadUserByUsername(String email) throws UsernameNotFoundException { User user = userService.getUserByEmail(email) .orElseThrow(() -> new UsernameNotFoundException(String.format("User with email=%s was not found", email))); return new CurrentUser(user); } } </code></pre> <p>正如你所看到的,只是要求<code>UserService</code>有一个电子邮件的用户。如果不存在,抛出异常。如果是,<code>CurrentUser</code>则返回对象。</p> <p>但是呢<code>CurrentUser</code>?它应该是<code>UserDetails</code>。那么,首先,这<code>UserDetails</code>只是一个接口:</p> <pre> <code class="language-java">public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); } </code></pre> <p>它描述了具有用户名,密码,<code>GrantedAuthority</code>对象列表以及某些不明确标志的用户,这些标志由于各种帐户无效的原因而被描述。</p> <p>所以我们需要返回一个实现。至少有一个是Spring Security提供的<code>org.springframework.security.core.userdetails.User</code>。</p> <p>可以使用它,但棘手的部分是将我们的<code>User</code>域对象与<code>UserDetails</code>授权可能需要相关联。它可以通过多种方式完成:</p> <ul> <li>使<code>User</code>域对象<code>UserDetails</code>直接实现 - 它将允许返回<code>User</code>完全按照收到的方式<code>UserService</code>。缺点就是用与Spring Security相关的代码来“污染”域对象。</li> <li>使用提供的实现<code>org.springframework.security.core.userdetails.User</code>,只需将<code>User</code>实体映射到它。这很好,但是有一些关于用户可用的附加信息,如<code>id</code>直接访问<code>role</code>或其他任何东西是很好的。</li> <li>因此,第三个解决方案是扩展提供的实现,并添加可能需要的任何信息,或者只是一个完整的<code>User</code>对象。</li> </ul> <p>最后一个选择是我在这里使用的,所以<code>CurrentUser</code>:</p> <pre> <code class="language-java">public class CurrentUser extends org.springframework.security.core.userdetails.User { private User user; public CurrentUser(User user) { super(user.getEmail(), user.getPasswordHash(), AuthorityUtils.createAuthorityList(user.getRole().toString())); this.user = user; } public User getUser() { return user; } public Long getId() { return user.getId(); } public Role getRole() { return user.getRole(); } } </code></pre> <p>...扩展<code>org.springframework.security.core.userdetails.User</code>,实现<code>UserDetails</code>。此外,它只是包装我们的<code>User</code>域对象,加入(可选)方便的方法是代理给它(<code>getRole()</code>,<code>getId()</code>,等)。</p> <p>这个映射很简单,就是在构造函数中发生的:</p> <ul> <li>在<code>UserDetails.username</code>从填充<code>User.email</code></li> <li>在<code>UserDetails.password</code>从填充<code>User.passwordHash</code></li> <li>被<code>User.role</code>转换为<code>String</code>,由<code>AuthorityUtils</code>辅助类包装在GrantedAuthority对象中。它将作为列表的唯一元素<code>UserDetails.getAuthorities()</code>被调用时可用。</li> <li>我决定忘记这些标志,因为我没有使用它们,它们都是按照原样返回<code>true</code>的<code>org.springframework.security.core.userdetails.User</code></li> </ul> <p>话虽如此,我必须提到,当你传递这样的包裹实体时,要小心,或者直接实现它们<code>UserDetails</code>。像这样泄漏域对象并不是一件正确的事情。这也可能是有问题的,即如果实体有一些LAZY获取的关联,那么在尝试获取Hibernate会话之外时可能遇到问题。</p> <p>或者,只需从实体复制足够的信息<code>CurrentUser</code>。足够这里足以授权用户,而无需为<code>User</code>实体调用数据库,因为这将增加执行授权的成本。</p> <p>无论如何,这应该使我们的登录表单工作。总而言之,一步一步地发生的是:</p> <ul> <li>用户打开<code>/login</code>URL。</li> <li>的<code>LoginController</code>返回与登录形式的图。</li> <li>用户填写表单,提交。</li> <li>Spring Security调用<code>CurrentUserDetailsService.loadUserByUsername()</code>用户名(在这种情况下为电子邮件)刚刚输入到表单中。</li> <li><code>CurrentUserDetailsService</code>从中获取用户<code>UserDetailsService</code>并返回<code>CurrentUser</code>。</li> <li>Spring Security调用<code>CurrentUser.getPassword()</code>并将密码哈希与表单中提供的散列密码进行比较。</li> <li>如果没关系,用户被重定向到他来的地方<code>/login</code>,如果不是,重定向到<code>/login?error</code>,再次处理<code>LoginController</code>。</li> <li>Spring Security'保持' <code>CurrentUser</code>对象(包裹<code>Authentication</code>)用于授权检查,或者您应该需要它。</li> </ul> <h4>记住我的身份验证</h4> <p>有时候,对于那些不想输入登录表单的懒惰者,即使他们的会话过期,也可以“记住我”身份验证。这样会造成安全隐患,所以只要建议您使用它。</p> <p>它的工作原理如下:</p> <ul> <li>用户登录,表单发布一个<code>remember-me</code>参数(在此应用程序中有一个复选框)</li> <li>Spring Security生成一个令牌,保存它,并将其发送给一个名为cookie的用户<code>remember-me</code>。</li> <li>下一次访问应用程序时,Spring Security会查找该cookie,如果它保存有效和未过期的令牌,它将自动验证该用户。</li> <li>此外,您可以通过“记住我”来确定用户是否被认证。</li> </ul> <p>为了启用它,它只需要一些补充<code>SecurityConfig.configure(HttpSecurity http)</code>,所以它将看起来像这样:</p> <pre> <code class="language-java">@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .formLogin() .loginPage("/login") .failureUrl("/login?error") .usernameParameter("email") .permitAll() .and() .logout() .logoutUrl("/logout") .deleteCookies("remember-me") .logoutSuccessUrl("/") .permitAll() .and() .rememberMe(); } </code></pre> <p>注意<code>rememberMe()</code>到底。它使整个事情。值得注意的是,默认情况下,令牌存储在内存中。这意味着当应用程序重新启动时,令牌将丢失。你可以使它们持久化,即存储在数据库中,但对于这个应用程序来说,这是足够的。</p> <p>另一项新的事情是<code>deleteCookies()</code>对<code>logout()</code>。<code>remember-me</code>一旦用户从应用程序中注销,就会强制删除该cookie。</p> <h4>授权</h4> <p>授权是一个过程,找出那个已经被授权的人,我们知道他的一切,可以访问应用程序提供的指定资源。</p> <p>在这个应用程序中,我们将这些信息包含在一个<code>CurrentUser</code>对象中。从安全的角度来说,重要的是<code>GrantedAuthority</code>他所拥有的。你记得我们直接从<code>User.role</code>字段填充,所以他们将是“USER”或“ADMIN”。</p> <h4>基于URL的授权</h4> <p>现在,根据要求,我们希望所有人都可以访问某些网址,有些则由授权人员管理,还有一些由管理员访问。</p> <p>它需要一些补充<code>SecurityConfig</code>:</p> <pre> <code class="language-java">@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/public/**").permitAll() .antMatchers("/users/**").hasAuthority("ADMIN") .anyRequest().fullyAuthenticated() .and() .formLogin() .loginPage("/login") .failureUrl("/login?error") .usernameParameter("email") .permitAll() .and() .logout() .logoutUrl("/logout") .deleteCookies("remember-me") .logoutSuccessUrl("/") .permitAll() .and() .rememberMe(); } </code></pre> <p>新的东西是电话<code>antMatchers()</code>。这强制执行:</p> <ul> <li>网址匹配<code>/</code>模式(此应用中的主页)和<code>/public/**</code>所有匹配的网址都将被允许。</li> <li>URL匹配<code>/users/**</code>模式(此应用程序中的列表视图)将仅允许具有“ADMIN”权限的用户。</li> <li>任何其他请求都需要经过身份验证的用户(“ADMIN”或“USER”)。</li> </ul> <h4>方法级授权</h4> <p>以上所有步骤都满足了大部分的要求,但是我们只能通过基于URL的授权无法解决。</p> <p>那是:</p> <ul> <li>管理员用户可以列出并查看所有用户,并创建新的用户</li> </ul> <p>这里的问题是,创建新用户的表单被映射到<code>/user/create</code>。我们可以添加这个URL <code>SecurityConfig</code>,但是如果我们做出一个习惯,我们最终会在配置文件中使用微型管理URL。我们也可以将其映射到<code>/users/create</code>这里,这可能在这里是有意义的,但是想到即将<code>/user/{id}/edit</code>来可能出现的那样。这是很好的离开它,因为它只是为了添加方法级别的授权给现有的方法<code>UserController</code>。</p> <p>要使方法级授权工作,<code>@EnableGlobalMethodSecurity(prePostEnabled = true)</code>需要将注释添加到<code>SecurityConfig</code>:</p> <pre> <code class="language-java">@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) class SecurityConfig extends WebSecurityConfigurerAdapter { // contents as before } </code></pre> <p>这使您可以在应用程序的任何层中对公共方法使用两个注释:</p> <ul> <li><code>@PreAuthorize("expression")</code>- 如果<code>expression</code>以<a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html" rel="nofollow" target="_blank">Spring表达式语言(Sprint)</a>编写的,它将评估为“true”,方法将被调用。</li> <li><code>@PostAuthorize("expression")</code> - 首先调用该方法,当检查失败时,403状态返回给用户。</li> </ul> <p>因此,为了解决第一个要求,处理用户创建的方法<code>UserController</code>需要注释<code>@PreAuthorize</code>:</p> <pre> <code class="language-java">@PreAuthorize("hasAuthority('ADMIN')") @RequestMapping(value = "/user/create", method = RequestMethod.GET) public ModelAndView getUserCreatePage() { // contents as before } @PreAuthorize("hasAuthority('ADMIN')") @RequestMapping(value = "/user/create", method = RequestMethod.POST) public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) { // contents as before } </code></pre> <p>该<code>hasAuthority()</code>规划环境地政司表达是由Spring Security的提供等等,即:</p> <ul> <li><code>hasAnyAuthority()</code>或者<code>hasAnyRole()</code>(“权限”和“角色”是Spring Security lingo中的同义词!) - 检查当前用户是否具有列表中的GrantedAuthority之一。</li> <li><code>hasAuthority()</code>或<code>hasRole()</code>- 如上所述,仅为一个。</li> <li><code>isAuthenticated()</code>或<code>isAnonymous()</code>- 当前用户是否被认证。</li> <li><code>isRememberMe()</code>或者<code>isFullyAuthenticated()</code>- 当前用户是否通过“记住我”令牌进行身份验证。</li> </ul> <h4>域对象安全</h4> <p>最后的要求是这样的:</p> <ul> <li>非管理员用户可以查看他们的信息,但不能窥视其他用户</li> </ul> <p>换句话说,我们需要一种方法来告诉用户什么时候可以请求<code>/user/{id}</code>。当用户是“ADMIN”(可以通过基于URL的身份验证来解决),而且当当前用户对自己发出请求时也是如此,这是不能解决的。</p> <p>这适用于以下方法<code>UserController</code>:</p> <pre> <code class="language-java"> @RequestMapping("/user/{id}") public ModelAndView getUserPage(@PathVariable Long id) { // contents as before } </code></pre> <p>我们需要检查当前用户是“管理” <strong>或</strong>在<code>id</code>传递给方法是一样的<code>id</code>从<code>User</code>与关联的域对象<code>CurrentUser</code>。由于<code>CurrentUser</code>有<code>User</code>实例包裹,我们有这方面的信息。所以我们可以用至少两种方法解决它:</p> <ul> <li>您可以使用Spel表达式来比较<code>id</code>传递给当前用户的方法<code>id</code>。</li> <li>您可以将此检查委托给服务 - 这是首选的,因为将来可能会改变条件,更好地在一个位置进行更改,而不是在使用注释的任何位置。</li> </ul> <p>要做到这一点,我<code>CurrentUserService</code>用这个界面创建了</p> <pre> <code class="language-java">public interface CurrentUserService { boolean canAccessUser(CurrentUser currentUser, Long userId); } </code></pre> <p>而这个实现:</p> <pre> <code class="language-java">@Service public class CurrentUserServiceImpl implements CurrentUserService { @Override public boolean canAccessUser(CurrentUser currentUser, Long userId) { return currentUser != null && (currentUser.getRole() == Role.ADMIN || currentUser.getId().equals(userId)); } } </code></pre> <p>现在在<code>@PreAuthorize</code>注释中使用它,只需放:</p> <pre> <code class="language-java">@PreAuthorize("@currentUserServiceImpl.canAccessUser(principal, #id)") @RequestMapping("/user/{id}") public ModelAndView getUserPage(@PathVariable Long id) { // contents as before } </code></pre> <p>哪个是用于调用具有principal(<code>CurrentUser</code>)实例的服务的SpEL表达式,并将<code>id</code>参数传递给方法。</p> <p>如果安全模型比较简单,这种方法可以正常工作。如果您发现自己写的东西类似于给予和检查多个访问权限,例如每个用户对多个域对象的查看/编辑/删除,那么最好是进入使用<a href="http://docs.spring.io/spring-security/site/docs/3.0.x/reference/domain-acls.html" rel="nofollow" target="_blank">Spring Security Domain对象ACL的方向</a>。</p> <p>这满足了所有的要求,此时应用程序得到了保障。</p> <h4>访问Spring Bean中的当前用户</h4> <p>无论何时需要从Controller或Service访问当前用户,都可以注入<code>Authentication</code>对象。可以通过调用获取当前的用户实例<code>Authentication.getPrincipal()</code>。</p> <p>这适用于构造函数或属性,如下所示:</p> <pre> <code class="language-java">@Autowired private Authentication authentication; void someMethod() { UserDetails currentUser = (UserDetails) authentication.getPrincipal(); } </code></pre> <p>此外,这适用于由<code>@RequestMapping</code>或者注释的控制器方法中的参数<code>@ModelAttribute</code>:</p> <pre> <code class="language-java">@RequestMapping("/") public String getMainPage(Authentication authentication) { UserDetails currentUser = (UserDetails) authentication.getPrincipal(); // something } </code></pre> <p>在这个应用程序中,可以直接转换<code>CurrentUser</code>,因为它<code>UserDetails</code>是正在使用的实际实现:</p> <pre> <code class="language-java">CurrentUser currentUser = (CurrentUser) authentication.getPrincipal(); </code></pre> <p>这样你也可以访问它所包围的域对象。</p> <h4>访问视图中的当前用户</h4> <p>访问当前身份验证的用户在视图中是有用的,即当为具有一定权限的用户呈现UI的某些元素时。</p> <p>在JSP中,可以使用安全标签库来完成,如下所示:</p> <pre> <code class="language-xml"><%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <sec:authentication property="principal.username" /> </code></pre> <p>这应该打印当前用户的名字。对于Freemarker,也可以使用该taglib,尽管它需要更多的功能。</p> <p>您还可以拧紧taglib,并将<code>Authentication</code>对象作为视图的模型属性传递:</p> <pre> <code class="language-java">@RequestMapping("/") public String getMainPage(@ModelProperty("authentication") Authentication authentication) { return "some_view"; } </code></pre> <p>但是为什么不为所有的观点,使用<code>@ControllerAdvice</code>,只是<code>UserDetails</code>从<code>Authentication</code>:</p> <pre> <code class="language-java">@ControllerAdvice public class CurrentUserControllerAdvice { @ModelAttribute("currentUser") public UserDetails getCurrentUser(Authentication authentication) { return (authentication == null) ? null : (UserDetails) authentication.getPrincipal(); } } </code></pre> <p>之后,您可以通过<code>currentUser</code>所有视图中的属性访问它。</p>
  • Spring Security OAuth 2开发者指南

    本文主要翻译spring官方的基于spring security框架的oauth2开发指南,spring,oauth2,spring框架,Java编程<h2>介绍</h2> <p>这是用户指南的支持<a href="https://tools.ietf.org/html/draft-ietf-oauth-v2" rel="external nofollow" target="_blank"><code>OAuth 2.0</code></a>。对于OAuth 1.0,一切都是不同的,所以<a href="http://projects.spring.io/spring-security-oauth/docs/oauth1.html" rel="external nofollow" target="_blank">看到它的用户指南</a>。</p> <p>本用户指南分为两部分,第一部分为OAuth 2.0提供者,第二部分为OAuth 2.0客户端。对于提供商和客户端,示例代码的最佳来源是<a href="https://github.com/spring-projects/spring-security-oauth/tree/master/tests" rel="external nofollow" target="_blank">集成测试</a>和<a href="https://github.com/spring-projects/spring-security-oauth/tree/master/samples/oauth2" rel="external nofollow" target="_blank">示例应用程序</a>。</p> <h2>OAuth 2.0提供程序</h2> <p>OAuth 2.0提供者机制负责公开OAuth 2.0受保护的资源。该配置包括建立可独立或代表用户访问其受保护资源的OAuth 2.0客户端。提供者通过管理和验证用于访问受保护资源的OAuth 2.0令牌来实现。在适用的情况下,提供商还必须提供用户界面,以确认客户端可以被授权访问受保护资源(即确认页面)。</p> <h2>OAuth 2.0提供程序实现</h2> <p>OAuth 2.0中的提供者角色实际上是在授权服务和资源服务之间分割的,而有时它们位于同一个应用程序中,使用Spring Security OAuth,您可以选择在两个应用程序之间进行拆分,并且还可以共享多个资源服务授权服务。令牌的请求由Spring MVC控制器端点处理,对受保护资源的访问由标准的Spring Security请求过滤器处理。为了实现OAuth 2.0授权服务器,Spring Security过滤器链中需要以下端点:</p> <ul> <li><a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.html" rel="external nofollow" target="_blank" title="授权终点"><code>AuthorizationEndpoint</code></a>用于服务授权请求。默认网址:<code>/oauth/authorize</code>。</li> <li><a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/endpoint/TokenEndpoint.html" rel="external nofollow" target="_blank" title="令牌终点"><code>TokenEndpoint</code></a>用于服务访问令牌的请求。默认网址:<code>/oauth/token</code>。</li> </ul> <p>实施OAuth 2.0资源服务器需要以下过滤器:</p> <ul> <li>将<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilter.html" rel="external nofollow" target="_blank" title="OAuth2AuthenticationProcessingFilter"><code>OAuth2AuthenticationProcessingFilter</code></a>用于加载给定的认证访问令牌请求的认证。</li> </ul> <p>对于所有OAuth 2.0提供程序功能,使用特殊的Spring OAuth <code>@Configuration</code>适配器简化了配置。还有一个用于OAuth配置的XML命名空间,并且模式位于<a href="http://www.springframework.org/schema/security/spring-security-oauth2.xsd" rel="external nofollow" target="_blank" title="oauth2.xsd">http://www.springframework.org/schema/security/spring-security-oauth2.xsd</a>。命名空间是<code>http://www.springframework.org/schema/security/oauth2</code>。</p> <h2>授权服务器配置</h2> <p>在配置授权服务器时,必须考虑客户端用于从最终用户获取访问令牌(例如授权代码,用户凭据,刷新令牌)的授权类型。服务器的配置用于提供客户端详细信息服务和令牌服务的实现,并且启用或禁用全局机制的某些方面。但是请注意,每个客户端都可以特别配置,以便能够使用某些授权机制和访问授权。也就是因为您的提供商配置为支持“客户端凭据”授权类型,并不意味着特定客户端被授权使用该授权类型。</p> <p>该<code>@EnableAuthorizationServer</code>注释用于配置OAuth 2.0授权服务器机制,以及任何<code>@Beans</code>实现<code>AuthorizationServerConfigurer</code>(有一个方便的适配器实现)。将以下功能委派给由Spring创建并传递到以下内容的单独配置程序<code>AuthorizationServerConfigurer</code>:</p> <ul> <li><code>ClientDetailsServiceConfigurer</code>:一个定义客户端详细信息服务的配置程序。客户端的详细信息可以初始化,也可以参考现有的存储。</li> <li><code>AuthorizationServerSecurityConfigurer</code>:定义令牌端点上的安全约束。</li> <li><code>AuthorizationServerEndpointsConfigurer</code>:定义授权和令牌端点和令牌服务。</li> </ul> <p>提供商配置的一个重要方面是授权代码提供给OAuth客户端(授权代码授权)的方式。授权代码由OAuth客户端通过将最终用户指向用户可以输入其凭据的授权页面获得,导致从提供商授权服务器重定向到具有授权码的OAuth客户端。这在OAuth 2规范中有详细说明。</p> <p>在XML中,有一个<code><authorization-server/></code>元素以类似的方式用于配置OAuth 2.0授权服务器。</p> <h3>配置客户端详细信息</h3> <p>将<code>ClientDetailsServiceConfigurer</code>(从您的回调<code>AuthorizationServerConfigurer</code>)可以用来在内存或JDBC实现客户的细节服务来定义的。客户端的重要属性是</p> <ul> <li><code>clientId</code>:(必填)客户端ID。</li> <li><code>secret</code>:(可信客户端需要)客户机密码(如果有)。</li> <li><code>scope</code>:客户受限的范围。如果范围未定义或为空(默认值),客户端不受范围限制。</li> <li><code>authorizedGrantTypes</code>:授予客户端使用授权的类型。默认值为空。</li> <li><code>authorities</code>授予客户的授权机构(普通的Spring Security权威机构)。</li> </ul> <p>客户端的详细信息可以通过直接访问底层商店(例如,在数据库表中<code>JdbcClientDetailsService</code>)或通过<code>ClientDetailsManager</code>接口(这两种实现<code>ClientDetailsService</code>也实现)来更新运行的应用程序。</p> <p>注意:JDBC服务的架构未与库一起打包(因为在实践中可能需要使用太多变体),而是可以从<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">github</a>中的<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">测试代码中</a>开始。</p> <h3>管理令牌</h3> <p>该<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/AuthorizationServerTokenServices.html" rel="external nofollow" target="_blank" title="AuthorizationServerTokenServices"><code>AuthorizationServerTokenServices</code></a>接口定义了所必需的管理OAuth 2.0令牌的操作。请注意以下事项:</p> <ul> <li>当创建访问令牌时,必须存储身份验证,以便接受访问令牌的资源可以稍后引用。</li> <li>访问令牌用于加载用于授权其创建的认证。</li> </ul> <p>在创建<code>AuthorizationServerTokenServices</code>实现时,您可能需要考虑使用<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/DefaultTokenServices.html" rel="external nofollow" target="_blank" title="DefaultTokenServices"><code>DefaultTokenServices</code></a>可插入的策略来更改访问令牌的格式和存储。默认情况下,它将通过随机值创建令牌,并处理除代表它的令牌持久化之外的所有内容<code>TokenStore</code>。默认存储是<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/store/InMemoryTokenStore.html" rel="external nofollow" target="_blank" title="InMemoryTokenStore">内存中的实现</a>,但还有一些其他可用的实现。这是一个关于每一个的一些讨论的描述</p> <ul> <li> <p>默认值<code>InMemoryTokenStore</code>对于单个服务器是完全正常的(即,在发生故障的情况下,低流量和热备份备份服务器)。大多数项目可以从这里开始,也可以在开发模式下运行,以便轻松启动没有依赖关系的服务器。</p> </li> <li> <p>这<code>JdbcTokenStore</code>是同一件事的<a href="http://projects.spring.io/spring-security-oauth/docs/JdbcTokenStore" rel="external nofollow" target="_blank">JDBC版本</a>,它将令牌数据存储在关系数据库中。如果您可以在服务器之间共享数据库,则可以使用JDBC版本,如果只有一个,则扩展同一服务器的实例,或者如果有多个组件,则授权和资源服务器。要使用<code>JdbcTokenStore</code>你需要“spring-jdbc”的类路径。</p> </li> <li> <p>商店的<a href="http://projects.spring.io/spring-security-oauth/docs/%60JwtTokenStore%60" rel="external nofollow" target="_blank">JSON Web令牌(JWT)版本</a>将所有关于授权的数据编码到令牌本身(因此,根本没有后端存储是一个显着的优势)。一个缺点是您不能轻易地撤销访问令牌,因此通常被授予短期到期权,撤销在刷新令牌处理。另一个缺点是,如果您在其中存储了大量用户凭据信息,令牌可能会变得非常大。这<code>JwtTokenStore</code>不是一个真正的“商店”,因为它不会保留任何数据,但它在翻译令牌值和验证信息之间起着相同的作用<code>DefaultTokenServices</code>。</p> </li> </ul> <p>注意:JDBC服务的架构未与库一起打包(因为在实践中可能需要使用太多变体),而是可以从<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">github</a>中的<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">测试代码中</a>开始。确保<code>@EnableTransactionManagement</code>在创建令牌时,防止在竞争相同行的客户端应用程序之间发生冲突。还要注意,示例模式有明确的<code>PRIMARY KEY</code>声明 - 这些在并发环境中也是必需的。</p> <h3>JWT令牌</h3> <p>要使用JWT令牌,您需要<code>JwtTokenStore</code>在授权服务器中。资源服务器还需要能够对令牌进行解码,因此它<code>JwtTokenStore</code>具有依赖性<code>JwtAccessTokenConverter</code>,并且授权服务器和资源服务器都需要相同的实现。默认情况下,令牌被签名,资源服务器还必须能够验证签名,因此它需要与授权服务器(共享密钥或对称密钥)相同的对称(签名)密钥,或者需要公共密钥(验证者密钥),其与授权服务器中的私钥(签名密钥)匹配(公私属或非对称密钥)。公钥(如果可用)由<code>/oauth/token_key</code>端点上的授权服务器公开,默认情况下,访问规则为“denyAll()”。<code>AuthorizationServerSecurityConfigurer</code></p> <p>要使用<code>JwtTokenStore</code>你需要的“spring-security-jwt”你的类路径(你可以在与Spring OAuth相同的github仓库中找到它,但发行周期不同)。</p> <h3>赠款类型</h3> <p><code>AuthorizationEndpoint</code>可以通过以下方式配置支持的授权类型<code>AuthorizationServerEndpointsConfigurer</code>。默认情况下,所有授权类型均受支持,除了密码(有关如何切换它的详细信息,请参见下文)。以下属性会影响授权类型:</p> <ul> <li><code>authenticationManager</code>:通过注入密码授权被打开<code>AuthenticationManager</code>。</li> <li><code>userDetailsService</code>:如果您注入<code>UserDetailsService</code>或者全局配置(例如a <code>GlobalAuthenticationManagerConfigurer</code>),则刷新令牌授权将包含对用户详细信息的检查,以确保该帐户仍然活动</li> <li><code>authorizationCodeServices</code>:定义<code>AuthorizationCodeServices</code>授权代码授权的授权代码服务(实例)。</li> <li><code>implicitGrantService</code>:在批准期间管理状态。</li> <li><code>tokenGranter</code>:(<code>TokenGranter</code>完全控制授予和忽略上述其他属性)</li> </ul> <p>在XML授予类型中包含作为子元素<code>authorization-server</code>。</p> <h3>配置端点URL</h3> <p>该<code>AuthorizationServerEndpointsConfigurer</code>有一个<code>pathMapping()</code>方法。它有两个参数:</p> <ul> <li>端点的默认(框架实现)URL路径</li> <li>需要的自定义路径(以“/”开头)</li> </ul> <p>由框架提供的URL路径<code>/oauth/authorize</code>(授权端点)<code>/oauth/token</code>(令牌端点)<code>/oauth/confirm_access</code>(用户发布批准此处)<code>/oauth/error</code>(用于在授权服务器中呈现错误)<code>/oauth/check_token</code>(由资源服务器用于解码访问令牌) ,并且<code>/oauth/token_key</code>(如果使用JWT令牌,则公开用于令牌验证的公钥)。</p> <p>注意,授权端点<code>/oauth/authorize</code>(或其映射替代方案)应使用Spring Security进行保护,以便只有经过身份验证的用户才能访问。例如使用标准的Spring Security <code>WebSecurityConfigurer</code>:</p> <pre> <code class="language-java"> @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests().antMatchers("/login").permitAll().and() // default protection for all resources (including /oauth/authorize) .authorizeRequests() .anyRequest().hasRole("USER") // ... more configuration, e.g. for form login } </code></pre> <p>注意:如果您的授权服务器也是资源服务器,那么还有另一个优先级较低的安全过滤器链控制API资源。通过访问令牌来保护这些请求,您需要他们的路径<em>不</em>与主用户面临的过滤器链中的路径匹配,因此请务必包含仅在<code>WebSecurityConfigurer</code>上述中选择非API资源的请求匹配器。</p> <p>默认情况下,通过Spring OAuth在<code>@Configuration</code>使用客户机密码的HTTP Basic认证的支持中为您保护令牌端点。在XML中不是这样(因此应该明确保护)。</p> <p>在XML中,<code><authorization-server/></code>元素具有一些可以用于以类似方式更改默认端点URL的属性。该<code>/check_token</code>端点必须(与显式启用<code>check-token-enabled</code>属性)。</p> <h2>自定义UI</h2> <p>大多数授权服务器端点主要由机器使用,但是有一些资源需要一个UI,而这些资源是GET <code>/oauth/confirm_access</code>和HTML响应<code>/oauth/error</code>。它们是在框架中使用白名单实现提供的,因此授权服务器的大多数真实世界实例都希望提供自己的实例,以便他们可以控制样式和内容。所有您需要做的是<code>@RequestMappings</code>为这些端点提供一个Spring MVC控制器,并且框架默认在调度程序中占用较低的优先级。在<code>/oauth/confirm_access</code>端点中,您可以期待<code>AuthorizationRequest</code>绑定到会话中,携带所有需要用户查询的数据(默认的实现是<code>WhitelabelApprovalEndpoint</code>这样查找起始点复制)。<code>/oauth/authorize</code>您可以从该请求中获取所有数据,然后根据需要进行渲染,然后所有用户需要执行的操作都是回复有关批准或拒绝授权的信息。请求参数直接传递给您<code>UserApprovalHandler</code>,<code>AuthorizationEndpoint</code>所以您可以随便解释数据。默认<code>UserApprovalHandler</code>取决于您是否已经提供了一个<code>ApprovalStore</code>在你的<code>AuthorizationServerEndpointsConfigurer</code>(在这种情况下,它是一个<code>ApprovalStoreUserApprovalHandler</code>)或不(在这种情况下,它是一个<code>TokenStoreUserApprovalHandler</code>)。标准审批处理程序接受以下内容:默认取决于您是否已经提供了一个在你的(在这种情况下,它是一个)或不(在这种情况下,它是一个)。标准审批处理程序接受以下内容:默认取决于您是否已经提供了一个在你的(在这种情况下,它是一个)或不(在这种情况下,它是一个)。标准审批处理程序接受以下内容:</p> <ul> <li> <p><code>TokenStoreUserApprovalHandler</code>:简单的是/否决定通过<code>user_oauth_approval</code>等于“真”或“假”。</p> </li> <li> <p><code>ApprovalStoreUserApprovalHandler</code>:一组<code>scope.*</code>参数键与“*”等于所请求的范围。参数的值可以是“true”或“approved”(如果用户批准了授权),则该用户被认为已经拒绝了该范围。如果批准了至少一个范围,则赠款是成功的。</p> </li> </ul> <p>注意:不要忘记在您为用户呈现的表单中包含CSRF保护。默认情况下,Spring Security正期待一个名为“_csrf”的请求参数(它在请求属性中提供值)。有关更多信息,请参阅Spring Security用户指南,或查看whitelabel实现的指导。</p> <h3>执行SSL</h3> <p>普通HTTP对于测试是很好的,但授权服务器只能在生产中使用SSL。您可以在安全容器或代理服务器后面运行应用程序,如果正确设置代理和容器(这与OAuth2无关),则应该可以正常运行。您也可能希望使用Spring Security <code>requiresChannel()</code>限制来保护端点。对于<code>/authorize</code>端点,由您来做,作为您正常应用程序安全性的一部分。对于<code>/token</code>端点<code>AuthorizationServerEndpointsConfigurer</code>,可以使用该<code>sslOnly()</code>方法设置一个标志。在这两种情况下,安全通道设置是可选的,但是如果Spring Security在不安全的通道上检测到请求,则会导致Spring Security重定向到安全通道。</p> <h2>自定义错误处理</h2> <p>授权服务器中的错误处理使用标准Spring MVC功能,即<code>@ExceptionHandler</code>端点本身的方法。用户还可以向<code>WebResponseExceptionTranslator</code>端点自身提供这些改变响应内容的最佳方式,而不是渲染方式。在授权<code>HttpMesssageConverters</code>端点的情况下,在令牌端点和OAuth错误视图(<code>/oauth/error</code>)的情况下,异常呈现(可以添加到MVC配置中)。该白色标签错误的端点提供了HTML的响应,但用户可能需要提供自定义实现(如只需添加一个<code>@Controller</code>带<code>@RequestMapping("/oauth/error")</code>)。</p> <h2>将用户角色映射到范围</h2> <p>限制令牌范围不仅仅是分配给客户端的范围,还可以根据用户自己的权限来进行限制。如果您在其中使用<code>DefaultOAuth2RequestFactory</code>,<code>AuthorizationEndpoint</code>则可以设置一个标志<code>checkUserScopes=true</code>,以将允许的范围限制为仅与那些与用户角色匹配的范围。你也可以注入<code>OAuth2RequestFactory</code>,<code>TokenEndpoint</code>但只有工作(即密码授权),如果你也安装一个<code>TokenEndpointAuthenticationFilter</code>- 你只需要在HTTP之后添加该过滤器<code>BasicAuthenticationFilter</code>。当然,您还可以实现自己的规则,将作用域映射到角色并安装自己的版本<code>OAuth2RequestFactory</code>。将<code>AuthorizationServerEndpointsConfigurer</code>让你注入一个定制的<code>OAuth2RequestFactory</code>,所以你可以使用该功能来建立一个工厂,如果你使用<code>@EnableAuthorizationServer</code>。</p> <h2>资源服务器配置</h2> <p>资源服务器(可以与授权服务器或单独的应用程序相同)提供受OAuth2令牌保护的资源。Spring OAuth提供了实现此保护的Spring Security认证过滤器。您可以<code>@EnableResourceServer</code>在<code>@Configuration</code>类上打开它,并使用a进行配置(必要时)<code>ResourceServerConfigurer</code>。可以配置以下功能:</p> <ul> <li><code>tokenServices</code>:定义令牌服务的bean(实例<code>ResourceServerTokenServices</code>)。</li> <li><code>resourceId</code>:资源的ID(可选,但建议并由验证服务器验证,如果存在)。</li> <li>其他扩展点(例如<code>tokenExtractor</code>从传入请求中提取令牌)</li> <li>请求匹配的受保护资源(默认为全部)</li> <li>受保护资源的访问规则(默认为“已验证”)</li> <li><code>HttpSecurity</code>Spring Security中配置程序允许的受保护资源的其他自定义</li> </ul> <p>该<code>@EnableResourceServer</code>注释添加类型的过滤器<code>OAuth2AuthenticationProcessingFilter</code>自动Spring Security的过滤器链。</p> <p>在XML中有一个<code><resource-server/></code>带有<code>id</code>属性的元素- 这是一个servlet的bean ID,<code>Filter</code>然后可以手动添加到标准的Spring Security链。</p> <p>您<code>ResourceServerTokenServices</code>是与授权服务器的合同的另一半。如果资源服务器和授权服务器在同一个应用程序中,然后使用,<code>DefaultTokenServices</code>那么您不需要太费心思考,因为它实现了所有必要的接口,因此它自动一致。如果您的资源服务器是一个单独的应用程序,那么您必须确保与授权服务器的功能相匹配,并提供一个<code>ResourceServerTokenServices</code>正确的解码令牌。与授权服务器一样,您经常可以使用该<code>DefaultTokenServices</code>选项,并且选择主要通过<code>TokenStore</code>(后端存储或本地编码)来表达。<code>RemoteTokenServices</code>一个替代方案是Spring OAuth功能(不是规范的一部分),允许资源服务器通过授权服务器(<code>/oauth/check_token</code>)上的HTTP资源解码令牌。<code>RemoteTokenServices</code>如果资源服务器中没有大量的流量(每个请求都必须与授权服务器进行验证),或者如果能够缓存结果,那么它们是方便的。要使用<code>/oauth/check_token</code>端点,您需要通过更改其访问规则(默认为“denyAll()”)来公开它<code>AuthorizationServerSecurityConfigurer</code>,例如</p> <pre> <code class="language-java"> @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess( "hasAuthority('ROLE_TRUSTED_CLIENT')"); } </code></pre> <p>在这个例子中,我们配置了<code>/oauth/check_token</code>端点和<code>/oauth/token_key</code>端点(所以信任的资源可以获得JWT验证的公钥)。这两个端点受到使用客户端凭据的HTTP基本身份验证的保护。</p> <h3>配置OAuth感知表达式处理程序</h3> <p>您可能希望利用Spring Security <a href="http://docs.spring.io/spring-security/site/docs/3.2.5.RELEASE/reference/htmlsingle/#el-access" rel="external nofollow" target="_blank" title="表达式访问控制">基于表达式的访问控制</a>。表达式处理程序将默认在<code>@EnableResourceServer</code>安装程序中注册。这些表达式包括<em>#oauth2.clientHasRole</em>,<em>#oauth2.clientHasAnyRole</em>和<em>#oath2.denyClient</em>,可用于根据oauth客户端的角色提供访问(请参阅<code>OAuth2SecurityExpressionMethods</code>全面的列表)。在XML中,您可以<code>expression-handler</code>使用常规<code><http/></code>安全配置的元素注册一个oauth感知表达式处理程序。</p> <h2>OAuth 2.0客户端</h2> <p>OAuth 2.0客户端机制负责访问其他服务器的OAuth 2.0保护资源。该配置包括建立用户可能访问的相关受保护资源。客户端还可能需要提供用于存储用户的授权码和访问令牌的机制。</p> <h3>受保护的资源配置</h3> <p>受保护的资源(或“远程资源”)可以使用类型的bean定义来定义<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java" rel="external nofollow" target="_blank"><code>OAuth2ProtectedResourceDetails</code></a>。受保护的资源具有以下属性:</p> <ul> <li><code>id</code>:资源的id。该id仅由客户端用于查找资源; 它在OAuth协议中从未使用过。它也被用作bean的id。</li> <li><code>clientId</code>:OAuth客户端ID。这是OAuth提供商识别您的客户端的ID。</li> <li><code>clientSecret</code>:与资源相关的秘密。默认情况下,没有密码为空。</li> <li><code>accessTokenUri</code>:提供访问令牌的提供者OAuth端点的URI。</li> <li><code>scope</code>:逗号分隔的字符串列表,指定对资源的访问范围。默认情况下,不指定范围。</li> <li><code>clientAuthenticationScheme</code>:您的客户端用于向访问令牌端点进行身份验证的方案。建议的值:“http_basic”和“form”。默认值为“http_basic”。请参阅OAuth 2规范的第2.1节。</li> </ul> <p>不同的授权类型具有不同的具体实现<code>OAuth2ProtectedResourceDetails</code>(例如<code>ClientCredentialsResource</code>,对于“client_credentials”授权类型)。对于需要用户授权的授权类型,还有一个其他属性:</p> <ul> <li><code>userAuthorizationUri</code>:如果用户需要授权访问资源,则用户将被重定向到的uri。请注意,这并不总是需要,具体取决于支持哪个OAuth 2配置文件。</li> </ul> <p>在XML中有一个<code><resource/></code>可以用来创建类型的bean的元素<code>OAuth2ProtectedResourceDetails</code>。它具有匹配上述所有属性的属性。</p> <h3>客户端配置</h3> <p>对于OAuth 2.0客户端,使用简化配置<code>@EnableOAuth2Client</code>。这有两件事情:</p> <ul> <li> <p>创建一个过滤器bean(带有ID <code>oauth2ClientContextFilter</code>)来存储当前的请求和上下文。在需要在请求期间进行身份验证的情况下,管理重定向到和从OAuth认证uri。</p> </li> <li> <p><code>AccessTokenRequest</code>在请求范围中创建一个类型的bean 。授权代码(或隐式)授权客户端可以使用这种方式来保持与个别用户的状态相关。</p> </li> </ul> <p>过滤器必须连接到应用程序中(例如,使用 同一名称的Servlet初始化程序或<code>web.xml</code>配置<code>DelegatingFilterProxy</code>)。</p> <p>本<code>AccessTokenRequest</code>可以在使用 <code>OAuth2RestTemplate</code>这样的:</p> <pre> <code class="language-java">@Autowired private OAuth2ClientContext oauth2Context; @Bean public OAuth2RestTemplate sparklrRestTemplate() { return new OAuth2RestTemplate(sparklr(), oauth2Context); } </code></pre> <p>在会话范围中放置OAuth2ClientContext(为您),以保持不同用户的状态分开。没有了,您将不得不自己在服务器上管理等效的数据结构,将传入的请求映射到用户,并将每个用户与单独的实例相关联<code>OAuth2ClientContext</code>。</p> <p>在XML中有一个<code><client/></code>带有<code>id</code>属性的元素- 这是一个servlet的bean id,<code>Filter</code>在这种<code>@Configuration</code>情况下必须映射为一个<code>DelegatingFilterProxy</code>(具有相同名称)。</p> <h3>访问受保护的资源</h3> <p>一旦您提供了资源的所有配置,您现在可以访问这些资源。用于访问这些资源的建议的方法是通过使用<a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html" rel="external nofollow" target="_blank" title="RestTemplate">所述<code>RestTemplate</code>在弹簧3引入</a>。Spring Security的OAuth提供<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java" rel="external nofollow" target="_blank">了</a>只需要提供一个实例的<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java" rel="external nofollow" target="_blank">RestTemplate的扩展</a><a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java" rel="external nofollow" target="_blank"><code>OAuth2ProtectedResourceDetails</code></a>。要使用用户令牌(授权代码授权),您应该考虑使用创建一些请求和会话作用域上下文对象的<code>@EnableOAuth2Client</code>配置(或XML等效项<code><oauth:rest-template/></code>),以便不同用户的请求在运行时不会相冲突。</p> <p>作为一般规则,Web应用程序不应使用密码授权,因此<code>ResourceOwnerPasswordResourceDetails</code>如果可以支持,请避免使用<code>AuthorizationCodeResourceDetails</code>。如果您非常需要从Java客户端工作的密码授权,则使用相同的机制来配置您的凭据,并将凭据<code>OAuth2RestTemplate</code>添加到<code>AccessTokenRequest</code>(这是一个<code>Map</code>短暂的),而不是<code>ResourceOwnerPasswordResourceDetails</code>(在所有访问令牌之间共享)。</p> <h3>在客户端中持久化令牌</h3> <p>客户端并不<em>需要</em>坚持令牌,但它可以很好的为不要求用户每次在客户端应用程序重新启动时批准新的代金券授予。该<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientTokenServices.java" rel="external nofollow" target="_blank"><code>ClientTokenServices</code></a>接口定义了所必需的持续的OAuth为特定用户2.0的令牌的动作。提供了一个JDBC实现,但如果您希望实现自己的服务来将持久性数据库中的访问令牌和关联的身份验证实例存储起来,那么您可以使用。如果要使用此功能,您需要提供一个专门配置<code>TokenProvider</code>的<code>OAuth2RestTemplate</code>如</p> <pre> <code class="language-java">@Bean @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) public OAuth2RestOperations restTemplate() { OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest)); AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider())); provider.setClientTokenServices(clientTokenServices()); return template; } </code></pre> <h2>外部OAuth2提供商客户端的定制</h2> <p>一些外部OAuth2提供者(例如<a href="https://developers.facebook.com/docs/authentication" rel="external nofollow" target="_blank" title="Facebook">Facebook</a>)不能正确地实现规范,或者他们只是坚持使用旧版本的规范,而不是Spring Security OAuth。要在客户端应用程序中使用这些提供程序,您可能需要调整客户端基础架构的各个部分。</p> <p>以Facebook为例,应用程序中有一个Facebook功能<code>tonr2</code>(您需要更改配置以添加您自己的,有效的客户端ID和密码 - 它们很容易在Facebook网站上生成)。</p> <p>Facebook令牌响应在令牌的到期时间(它们使用<code>expires</code>而不是<code>expires_in</code>)中也包含不符合规定的JSON条目,因此,如果要在应用程序中使用到期时间,则必须使用自定义手动解码<code>OAuth2SerializationService</code>。</p>
  • artDialog v7使用说明及API文档

    artDialog v7 使用说明及API文档,artDialog ,artDialog具有bootstrap风格的弹窗组件<h1>artDialog具有bootstrap风格的弹窗组件</h1> <img alt="artdialog1" class="img-thumbnail" src="/assist/images/blog/859e118e483b47189690b6836e9d8416.png" /><br /> <img alt="artdialog2" class="img-thumbnail" src="/assist/images/blog/02fcbce67bc649a2b475ede25c9a3fb6.png" /><br /> <img alt="artdialog3" class="img-thumbnail" src="/assist/images/blog/c31c53851106483f908c0eb7394f4eab.png" /><br /> <img alt="artdialog4" class="img-thumbnail" src="/assist/images/blog/1875d350bcf54980b6206de142705dec.png" /><br /> <img alt="artdialog5" class="img-thumbnail" src="/assist/images/blog/b16cadd97d0448b79ac83ee18ac45630.png" /><br /> <img alt="artdialog6" class="img-thumbnail" src="/assist/images/blog/c267c1d7647340b7851a79c9514ff708.png" /><br />   <p>首页 > 文档与示例</p> <p>artDialog —— 经典、优雅的网页对话框控件。</p> <ol> <li>支持普通与 12 方向气泡状对话框</li> <li>完善的焦点处理,自动焦点附加与回退</li> <li>支持 ARIA 标准</li> <li>面向未来:基于 HTML5 Dialog 的 API</li> <li>支持标准与模态对话框</li> <li>丰富且友好的编程接口</li> <li>能自适应内容尺寸</li> </ol> <h2>引入 artDialog</h2> <h3>1.直接引用</h3> <pre> <code class="language-javascript"><script src="lib/jquery-1.10.2.js"></script> <script src="dist/dialog.js"></script></code></pre> <h3>2.作为 Webpack、RequireJS 或 SeaJS 的模块引入</h3> <p>使用 Npm 安装 <code>art-dialog</code></p> <pre> <code>npm install --save-dev art-dialog </code></pre> <pre> <code>var dialog = require('art-dialog');</code></pre> <p>**注意:**内部依赖全局模块<code>require('jquery')</code>,请注意全局模块配置是否正确</p> <ul> <li>如果需要支持 iframe 内容与拖拽,请引用加强版 dialog-plus.js</li> <li>jquery 最低要求版本为<code>1.7+</code></li> </ul> <h2>快速参考</h2> <h3>普通对话框</h3> <pre> <code class="language-javascript">var d = dialog({ title: '欢迎', content: '欢迎使用 artDialog 对话框组件!' }); d.show(); </code></pre> 运行 <h3>模态对话框</h3> <pre> <code class="language-javascript">var d = dialog({ title: 'message', content: '<input autofocus />' }); d.showModal(); </code></pre> 运行 <h3>气泡浮层</h3> <pre> <code class="language-javascript">var d = dialog({ content: 'Hello World!', quickClose: true// 点击空白处快速关闭 }); d.show(document.getElementById('quickref-bubble')); </code></pre> 运行 <p>12 个方向定位演示</p> <h3>添加按钮</h3> <p>1.确定与取消按钮:</p> <pre> <code class="language-javascript">var d = dialog({ title: '提示', content: '按钮回调函数返回 false 则不许关闭', okValue: '确定', ok: function () { this.title('提交中…'); return false; }, cancelValue: '取消', cancel: function () {} }); d.show(); </code></pre> 运行 <p>2.指定更多按钮:</p> <p>请参考 <code>button</code> 方法或参数。</p> <h3>控制对话框关闭</h3> <pre> <code class="language-javascript">var d = dialog({ content: '对话框将在两秒内关闭' }); d.show(); setTimeout(function () { d.close().remove(); }, 2000); </code></pre> 运行 <h3>给对话框左下角添加复选框</h3> <pre> <code class="language-javascript">var d = dialog({ title: '欢迎', content: '欢迎使用 artDialog 对话框组件!', ok: function () {}, statusbar: '<label><input type="checkbox">不再提醒</label>' }); d.show(); </code></pre> 运行 <h3>点按钮不关闭对话框</h3> <p>按钮事件返回 false 则不会触发关闭。</p> <pre> <code class="language-javascript">var d = dialog({ title: '欢迎', content: '欢迎使用 artDialog 对话框组件!', ok: function () { var that = this; this.title('正在提交..'); setTimeout(function () { that.close().remove(); }, 2000); return false; }, cancel: function () { alert('不许关闭'); return false; } }); d.show(); </code></pre> 运行 <h3>不显示关闭按钮</h3> <pre> <code class="language-javascript">var d = dialog({ title: '欢迎', content: '欢迎使用 artDialog 对话框组件!', cancel: false, ok: function () {} }); d.show(); </code></pre> 运行 <h3>创建 iframe 内容</h3> <p>artDialog 提供了一个增强版用来支持复杂的 iframe 套嵌的页面,可以在顶层页面创建一个可供 iframe 访问的对话框创建方法,例如:</p> <pre> <code class="language-javascript">require(['art-dialog/dist/dialog-plus'], function (dialog) { window.dialog = dialog; });</code></pre> <p>然后子页面就可以通过<code>top.dialog</code>控制对话框了。</p> <p>打开示例页面</p> <p>小提示:增强版的选项比标准版多了<code>url</code>、<code>oniframeload</code>这几个字段。</p> <h2>方法</h2> <p>若无特别说明,方法均支持链式调用。</p> <h3>show([anchor])</h3> <p>显示对话框。</p> <p>默认居中显示,支持传入元素节点或者事件对象。</p> <ul> <li>参数类型为<code>HTMLElement</code>:可吸附到元素上,同时对话框将呈现气泡样式。</li> <li>参数类型为<code>Event Object</code>:根据<code>event.pageX</code>与<code>event.pageY</code>定位。</li> </ul> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog(); d.content('hello world'); d.show(document.getElementById('api-show')); </code></pre> 运行 <pre> <code class="language-javascript">var d = dialog({ id: 'api-show-dialog', quickClose: true, content: '右键菜单' }); $(document).on('contextmenu', function (event) { d.show(event); return d.destroyed; }); </code></pre> 运行 <h3>showModal([anchor])</h3> <p>显示一个模态对话框。</p> <p>其余特性与参数可参见<code>show([anchor])</code>方法。</p> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog({ title: 'message', content: '<input autofocus />' }); d.showModal(); </code></pre> 运行 <h3>close([result])</h3> <p>关闭(隐藏)对话框。</p> <p>可接收一个返回值,可以参见 returnValue。</p> <p><strong>注意</strong>:<code>close()</code>方法只隐藏对话框,不会在 DOM 中删除,删除请使用<code>remove()</code>方法。</p> <h3>remove()</h3> <p>销毁对话框。</p> <p><strong>注意</strong>:不同于<code>close([result])</code>方法,<code>remove()</code>方法会从 DOM 中移出对话框相关节点,销毁后的对话框无法再次使用。</p> <p>对话框按钮点击后默认会依次触发 <code>close()</code>、<code>remove()</code> 方法。如果想手动控制对话框关闭可以如下操作:</p> <pre> <code class="language-javascript">var d = dialog(); // [..] d.close().remove(); </code></pre> 运行 <h3>content(html)</h3> <p>写入对话框内容。</p> <p><code>html</code>参数支持<code>String</code>、<code>HTMLElement</code>类型。</p> <h4>示例</h4> <p>传入字符串:</p> <pre> <code class="language-javascript">var d = dialog(); d.content('hello world'); d.show(); </code></pre> 运行 <p>传入元素节点:</p> <pre> <code class="language-javascript">var elem = document.getElementById('test'); dialog({ content: elem, id: 'EF893L' }).show(); </code></pre> <p>v6.0.4 更新:隐藏元素将会自动显示,并且对话框卸载的时候会放回到<code>body</code>中</p> <h3>title(text)</h3> <p> </p> <p>写入对话框标题。</p> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog(); d.title('hello world'); d.show(); </code></pre> 运行 <h3>width(value)</h3> <p>设置对话框宽度。</p> <h3>示例</h3> <pre> <code class="language-javascript">dialog({ content: 'hello world' }) .width(320) .show(); </code></pre> 运行 <h3>height(value)</h3> <p> </p> <p>设置对话框高度。</p> <h3>示例</h3> <pre> <code class="language-javascript">dialog({ content: 'hello world' }) .height(320) .show(); </code></pre> 运行 <h3>reset()</h3> <p> </p> <p>手动刷新对话框位置。</p> <p>通常动态改变了内容尺寸后需要刷新对话框位置。</p> <h3>button(args)</h3> <p> </p> <p>自定义按钮。</p> <p>参数请参考 选项<code>button</code>;同时支持传入 HTML 字符串填充按钮区域。</p> <h3>focus()</h3> <p> </p> <p>聚焦对话框(置顶)。</p> <h3>blur()</h3> <p> </p> <p>让对话框失去焦点。</p> <h3>addEventListener(type, callback)</h3> <p> </p> <p>添加事件。</p> <p>支持的事件有:<code>show</code>、<code>close</code>、<code>beforeremove</code>、<code>remove</code>、<code>reset</code>、<code>focus</code>、<code>blur</code></p> <h3>removeEventListener(type, callback)</h3> <p> </p> <p>删除事件。</p> <h3>dialog.get(id)</h3> <p> </p> <p>根据获取打开的对话框实例。</p> <p><strong>注意</strong>:这是一个静态方法。</p> <h3>dialog.getCurrent()</h3> <p> </p> <p>获取当前(置顶)对话框实例。</p> <p><strong>注意</strong>:这是一个静态方法。</p> <h2>配置参数</h2> <p> </p> <h3>content</h3> <p> </p> <p>设置消息内容。</p> <h4>类型</h4> <p>String, HTMLElement</p> <h4>示例</h4> <p>传入字符串:</p> <pre> <code class="language-javascript">dialog({ content: 'hello world!' }).show(); </code></pre> 运行 <p>传入元素节点:</p> <pre> <code class="language-javascript">var elem = document.getElementById('test'); dialog({ content: elem, id: 'EF893L' }).show(); </code></pre> <h3>title</h3> <p> </p> <p>标题内容。</p> <h4>类型</h4> <p>String</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ title: 'hello world!' }).show(); </code></pre> 运行 <h3>statusbar</h3> <p> </p> <p>状态栏区域 HTML 代码。</p> <p>可以实现类似“不再提示”的复选框。<strong>注意</strong>:必须有按钮才会显示。</p> <h4>类型</h4> <p>String</p> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog({ title: '欢迎', content: '欢迎使用 artDialog 对话框组件!', ok: function () {}, statusbar: '<label><input type="checkbox">不再提醒</label>' }); d.show(); </code></pre> 运行 <h3>ok</h3> <p> </p> <p>确定按钮。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象,执行完毕默认关闭对话框,若返回 false 则阻止关闭。</p> <h4>类型</h4> <p>Function</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ ok: function () { this .title('消息') .content('hello world!') .width(130); return false; } }).show(); </code></pre> 运行 <h3>okValue</h3> <p> </p> <p>(默认值: <code>"ok"</code>) 确定按钮文本。</p> <h4>类型</h4> <p>String</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ okValue: '猛击我', ok: function () { this.content('hello world!'); return false; } }).show(); </code></pre> 运行 <h3>cancel</h3> <p> </p> <p>取消按钮。</p> <p>取消按钮也等同于标题栏的关闭按钮,若值为<code>false</code>则不显示关闭按钮。回调函数<code>this</code>指向<code>dialog</code>对象,执行完毕默认关闭对话框,若返回<code>false</code>则阻止关闭。</p> <h4>类型</h4> <p>Function, Boolean</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ title: '消息', ok: function () {}, cancel: function () { alert('取消'); } }).show(); </code></pre> 运行 <pre> <code class="language-javascript">dialog({ title: '消息', content: '不显示关闭按钮', ok: function () {}, cancel: false }).show(); </code></pre> 运行 <h3>cancelValue</h3> <p> </p> <p>(默认值: <code>"cancel"</code>) 取消按钮文本。</p> <h4>类型</h4> <p>String</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ cancelValue: '取消我', cancel: function () { alert('关闭'); } }).show(); </code></pre> 运行 <h3>cancelDisplay</h3> <p> </p> <p>(默认值: <code>true</code>) 是否显示取消按钮。</p> <h4>类型</h4> <p>Boolean</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ title: '提示', content: '这是一个禁止关闭的对话框,并且没有取消按钮', cancel: function () { alert('禁止关闭'); return false; }, cancelDisplay: false }).show(); </code></pre> 运行 <h3>button</h3> <p> </p> <p>自定义按钮组。</p> <h4>类型</h4> <p>Array</p> <h4>选项</h4> <table class="table table-bordered table-hover"> <thead> <tr> <th>名称</th> <th>类型</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>value</td> <td>String</td> <td>按钮显示文本</td> </tr> <tr> <td>callback</td> <td>Function</td> <td>(可选) 回调函数<code>this</code>指向<code>dialog</code>对象,执行完毕默认关闭与销毁对话框(依次执行<code>close()</code>与<code>remove()</code>),若返回<code>false</code>则阻止关闭与销毁</td> </tr> <tr> <td>autofocus</td> <td>Boolean</td> <td>(默认值:<code>false</code>) 是否自动聚焦</td> </tr> <tr> <td>disabled</td> <td>Boolean</td> <td>(默认值: <code>false</code>) 是否禁用</td> </tr> </tbody> </table> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ button: [ { value: '同意', callback: function () { this .content('你同意了'); return false; }, autofocus: true }, { value: '不同意', callback: function () { alert('你不同意') } }, { id: 'button-disabled', value: '无效按钮', disabled: true }, { value: '关闭我' } ] }).show(); </code></pre> 运行 <h3>width</h3> <p> </p> <p>设置对话框 <strong>内容</strong> 宽度。</p> <h4>类型</h4> <p>String, Number</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ width: 460 }).show(); </code></pre> 运行 <pre> <code class="language-javascript">dialog({ width: '20em' }).show(); </code></pre> 运行 <h3>height</h3> <p> </p> <p>设置对话框 <strong>内容</strong> 高度。</p> <h4>类型</h4> <p>String, Number</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ height: 460 }).show(); </code></pre> 运行 <pre> <code class="language-javascript">dialog({ height: '20em' }).show(); </code></pre> 运行 <h3>skin</h3> <p> </p> <p>设置对话框额外的<code>className</code>参数。</p> <p>多个<code>className</code>请使用空格隔开。</p> <h4>类型</h4> <p>String</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ skin: 'min-dialog tips' }).show(); </code></pre> <h3>padding</h3> <p> </p> <p>(默认值: <em>继承 css 文件设置</em>) 设置消息内容与消息容器的填充边距,即 style <code>padding</code>属性</p> <h4>类型</h4> <p>String</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ content: 'hello world', padding: 0 }).show(); </code></pre> 运行 <h3>fixed</h3> <p> </p> <p>(默认值: <code>false</code>) 开启固定定位。</p> <p>固定定位是 css2.1 <code>position</code>的一个属性,它能固定在浏览器某个地方,也不受滚动条拖动影响。IE6 与部分移动浏览器对其支持不好,内部会转成绝对定位。</p> <h4>类型</h4> <p>Boolean</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ fixed: true, title: '消息', content: '请拖动滚动条查看' }).show(); </code></pre> 运行 <h3>align</h3> <p> </p> <p>(默认值: <code>"bottom left"</code>) 设置对话框与其他元素的对齐方式。</p> <p>如果<code>show(elem)</code>与<code>showModal(elem)</code>传入元素,<code>align</code>参数方可生效,支持如下对齐方式:</p> <ul> <li><code>"top left"</code></li> <li><code>"top"</code></li> <li><code>"top right"</code></li> <li><code>"right top"</code></li> <li><code>"right"</code></li> <li><code>"right bottom"</code></li> <li><code>"bottom right"</code></li> <li><code>"bottom"</code></li> <li><code>"bottom left"</code></li> <li><code>"left bottom"</code></li> <li><code>"left"</code></li> <li><code>"left top"</code></li> </ul> <h3>类型</h3> <p>String</p> <h3>示例</h3> <pre> <code class="language-javascript">var d = dialog({ align: 'left', content: 'Hello World!', quickClose: true }); d.show(document.getElementById('option-align')); </code></pre> 运行 <p>12 个方向定位演示</p> <h3>autofocus</h3> <p> </p> <p>(默认值: <code>true</code>) 是否支持自动聚焦。</p> <h4>类型</h4> <p>Boolean</p> <h3>quickClose</h3> <p> </p> <p>(默认值: false) 是否点击空白出快速关闭。</p> <h4>类型</h4> <p>Boolean</p> <h3>示例</h3> <pre> <code class="language-javascript">var d = dialog({ content: '点击空白处快速关闭', quickClose: true }); d.show(document.getElementById('option-quickClose')); </code></pre> 运行 <h3>zIndex</h3> <p> </p> <p>(默认值: <code>1024</code>) 重置全局<code>zIndex</code>初始值,用来改变对话框叠加高度。</p> <p>比如有时候配合外部浮动层 UI 组件,但是它们可能默认<code>zIndex</code>没有对话框高,导致无法浮动到对话框之上,这个时候你就可以给对话框指定一个较小的<code>zIndex</code>值。</p> <p>请注意这是一个会影响到全局的配置,后续出现的对话框叠加高度将重新按此累加。</p> <h4>类型</h4> <p>Number</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ zIndex: 87 }).show(); </code></pre> 运行 <h3>onshow</h3> <p> </p> <p>对话框打开的事件。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象。</p> <h4>类型</h4> <p>Function</p> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog({ content: 'loading..', onshow: function () { this.content('dialog ready'); } }); d.show(); </code></pre> 运行 <h3>onclose</h3> <p> </p> <p>对话框关闭后执行的事件。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象。</p> <h4>类型</h4> <p>Function</p> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog({ onclose: function () { alert('对话框已经关闭'); }, ok: function () {} }); d.show(); </code></pre> 运行 <h3>onbeforeremove</h3> <p> </p> <p>对话框销毁之前事件。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象。</p> <h4>类型</h4> <p>Function</p> <h3>onremove</h3> <p> </p> <p>对话框销毁事件。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象。</p> <h4>类型</h4> <p>Function</p> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog({ onclose: function () { alert('对话框已经关闭'); }, onremove: function () { alert('对话框已经销毁'); }, ok: function () {} }); d.show(); </code></pre> 运行 <h3>onfocus</h3> <p> </p> <p>对话框获取焦点事件。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象。</p> <h4>类型</h4> <p>Function</p> <h3>onblur</h3> <p> </p> <p>对话框失去焦点事件。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象。</p> <h4>类型</h4> <p>Function</p> <h3>onreset</h3> <p> </p> <p>对话框位置重置事件。</p> <p>回调函数<code>this</code>指向<code>dialog</code>对象。</p> <h4>类型</h4> <p>Function</p> <h3>id</h3> <p> </p> <p>设定对话框唯一标识。</p> <ol> <li>可防止重复 ID 对话框弹出。</li> <li>支持使用<code>dialog.get(id)</code>获取某个对话框的接口。</li> </ol> <h4>类型</h4> <p>String</p> <h4>示例</h4> <pre> <code class="language-javascript">dialog({ id: 'id-demo', content: '再次点击运行看看' }).show(); dialog.get('id-demo').title('8888888888'); </code></pre> 运行 <h2>属性</h2> <h3>open</h3> <p>判断对话框是否被打开。</p> <h3>returnValue</h3> <p>对话框返回值。</p> <h4>示例</h4> <pre> <code class="language-javascript">var d = dialog({ title: '消息', content: '<input id="property-returnValue-demo" value="1" />', ok: function () { var value = $('#property-returnValue-demo').val(); this.close(value); this.remove(); } }); d.addEventListener('close', function () { alert(this.returnValue); }); d.show(); </code></pre> 运行
  • Java编程之Apache Shiro Web支持

    组态,将Shiro集成到任何Web应用程序中的最简单的方法是在web.xml中配置Servlet ContextListener和Filter,了解如何读取Shiro的INI配置。<h1>组态</h1> <p>将Shiro集成到任何Web应用程序中的最简单的方法是在web.xml中配置Servlet ContextListener和Filter,了解如何读取Shiro的INI配置。大部分INI配置格式本身在配置页面的INI Sections部分中定义,但我们将在此处介绍一些其他特定于Web的部分。</p>  <strong>使用Spring?</strong> <hr /> <p>Spring Framework用户不会执行此设置。如果你使用Spring,你将需要阅读关于Spring特定的web配置。</p>   <h3>web.xml</h3> <h4>Shiro 1.2以后</h4> <p>在Shiro 1.2及更高版本中,标准Web应用程序通过添加以下XML块来初始化Shiro <code>web.xml</code>:</p> <pre> <code class="language-xml"><listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> ... <filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> </code></pre> <p>这假设一个Shiro INI 配置文件位于以下两个位置之一,使用以前找到的位置:</p> <ol> <li><code>/WEB-INF/shiro.ini</code></li> <li><code>shiro.ini</code> 文件在类路径的根。</li> </ol> <p>这里是上面的配置:</p> <ul> <li> <p>的<code>EnvironmentLoaderListener</code>初始化四郎<code>WebEnvironment</code>实例(包含一切四郎需要操作,包括<code>SecurityManager</code>),并使其在访问<code>ServletContext</code>。如果您需要随时获取此<code>WebEnvironment</code>实例,则可以调用<code>WebUtils.getRequiredWebEnvironment(servletContext)</code>。</p> </li> <li> <p>该<code>ShiroFilter</code>会利用这个<code>WebEnvironment</code>来执行所有必要的安全操作的任何过滤的要求。</p> </li> <li> <p>最后,该<code>filter-mapping</code>定义确保所有请求都被过滤<code>ShiroFilter</code>,建议大多数Web应用程序使用,以确保任何请求都是安全的。</p> </li> </ul>  <strong>ShiroFilter过滤器映射</strong> <hr /> <p>通常希望在任何其他“filter-mapping”声明之前定义“ShiroFilter filter-mapping”,以确保Shiro也能在这些过滤器中运行。</p>   <h5>自定义<code>WebEnvironment</code>类</h5> <p>默认情况下,<code>EnvironmentLoaderListener</code>将创建一个<code>IniWebEnvironment</code>实例,它承担Shiro的基于INI的配置。如果愿意,您可以<code>WebEnvironment</code>通过在<code>ServletContext</code> <code>context-param</code>中指定一个自定义实例来指定<code>web.xml</code>:</p> <pre> <code class="language-xml"><context-param> <param-name>shiroEnvironmentClass</param-name> <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value> </context-param> </code></pre> <p>这允许您自定义如何解析配置格式并将其表示为<code>WebEnvironment</code>实例。您可以对现有<code>IniWebEnvironment</code>的自定义行为进行子类化,或者完全支持不同的配置格式。例如,如果有人想要在XML而不是INI中配置Shiro,他们可以创建一个基于XML的实现,例如<code>com.foo.bar.shiro.XmlWebEnvironment</code>。</p> <h5>自定义配置位置</h5> <p>本<code>IniWebEnvironment</code>类希望读取并加载INI配置文件。默认情况下,此类将自动查找以下两个位置的Shiro <code>.ini</code>配置(按顺序):</p> <ol> <li><code>/WEB-INF/shiro.ini</code></li> <li><code>classpath:shiro.ini</code></li> </ol> <p>它将使用先找到的。</p> <p>然而,如果你希望将你的配置在其他位置,则可能与另一指定位置<code>context-param</code>在<code>web.xml</code>:</p> <pre> <code class="language-xml"><context-param> <param-name>shiroConfigLocations</param-name> <param-value>YOUR_RESOURCE_LOCATION_HERE</param-value> </context-param> </code></pre> <p>默认情况下,<code>param-value</code>期望由<code>ServletContext.getResource</code>方法定义的规则可解析。例如,<code>/WEB-INF/some/path/shiro.ini</code></p> <p>但是,您也可以使用Shiro的ResourceUtils类支持的适当资源前缀来指定特定的文件系统,类路径或URL位置,例如:</p> <ul> <li><code>file:/home/foobar/myapp/shiro.ini</code></li> <li><code>classpath:com/foo/bar/shiro.ini</code></li> <li><code>url:http://confighost.mycompany.com/myapp/shiro.ini</code></li> </ul> <h4>Shiro 1.1和更早版本</h4> <p>在1.1或更早版本的Web应用程序中启用Shiro的最简单的方法是定义IniShiroFilter并指定<code>filter-mapping</code>:</p> <pre> <code class="language-xml"><filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> </filter> ... <!-- Make sure any request you want accessible to Shiro is filtered. /* catches all --> <!-- requests. Usually this filter mapping is defined first (before all others) to --> <!-- ensure that Shiro works in subsequent filters in the filter chain: --> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> </code></pre> <p>此定义要求您的INI配置位于类路径(例如<code>classpath:shiro.ini</code>)的根目录下的shiro.ini文件中。</p> <h5>自定义路径</h5> <p>如果不想将INI配置放入<code>/WEB-INF/shiro.ini</code>或<code>classpath:shiro.ini</code>,您可以根据需要指定自定义资源位置。添加<code>configPath init-param</code>并指定资源位置:</p> <pre> <code class="language-xml"><filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> <init-param> <param-name>configPath</param-name> <param-value>/WEB-INF/anotherFile.ini</param-value> </init-param> </filter> ... </code></pre> <p>未限定(无限额或“非前缀”)<code>configPath</code>值被假定为<code>ServletContext</code>资源路径,可通过该<br /> <code>ServletContext.getResource</code>方法定义的规则解析。</p>  <strong>ServletContext资源路径 - Shiro 1.2+</strong> <hr /> <p>ServletContext资源路径在Shiro 1.2和更高版本中可用。在1.1和更早的版本中,所有<code>configPath</code>的定义必须指定<code>classpath:</code>,<code>file:</code>或<code>url:</code>前缀。</p> <p>你也可以指定其他非<code>ServletContext</code>使用资源位置<code>classpath:</code>,<code>url:</code>或<code>file:</code>前缀分别表示classpath中,URL或文件系统位置。例如:</p> <pre> <code class="language-xml">... <init-param> <param-name>configPath</param-name> <param-value>url:http://configHost/myApp/shiro.ini</param-value> </init-param> ... </code></pre> <h5>内联配置</h5> <p>最后,还可以将INI配置嵌入到web.xml中,而不使用INI文件。您可以使用<code>config init-param</code>而不是<code>configPath</code>:</p> <pre> <code class="language-xml"><filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> <init-param><param-name>config</param-name><param-value> # INI Config Here </param-value></init-param> </filter> ... </code></pre> <p>直列配置往往细小型或简单的应用,但它通常是更方便的外部化它的原因如下专用shiro.ini文件:</p> <ul> <li>您可以编辑安全配置很多,并且不想将修订控制'noise'添加到web.xml文件</li> <li>您可能希望将安全配置与web.xml配置的其余部分分离</li> <li>您的安全配置可能会变大,您希望保持web.xml精简并更易于阅读</li> <li>你有一个复杂的构建系统,其中相同的shiro配置可能需要在多个地方引用</li> </ul> <p>这取决于你 - 使用什么对你的项目有意义。</p> <h3>Web INI配置</h3> <p>除了标准<code>[main]</code>,<code>[users]</code>并<code>[roles]</code>在主已经说明部分配置章节中,你还可以指定一个特定的网络<code>[urls]</code>在部分<code>shiro.ini</code>文件:</p> <pre> <code># [main], [users] and [roles] above here ... [urls] ... </code></pre> <p>该<code>[urls]</code>部分允许你做一些事情,不以任何Web框架存在,我们见过的:确定特设的过滤器链在应用程序中任何匹配的URL路径的能力!</p> <p>这是<em>远远</em>比你通常定义过滤链更灵活,功能强大,简洁的<code>web.xml</code>:即使你从未使用过任何其他的功能,该功能提供四郎也只有这个使用,仅此一项就使值得使用。</p> <h4>[urls]</h4> <p>该<code>urls</code>节中每行的格式如下:</p> <pre> <code>_URL_Ant_Path_Expression_ = _Path_Specific_Filter_Chain_ </code></pre> <p>例如:</p> <pre> <code>... [urls] /index.html = anon /user/create = anon /user/** = authc /admin/** = authc, roles[administrator] /rest/** = authc, rest /remoting/rpc/** = authc, perms["remote:invoke"] </code></pre> <p>接下来,我们将详细介绍这些行的含义。</p> <p>等号(=)左侧的令牌是相对于Web应用程序的上下文根的Ant样式路径表达式。</p> <p>例如,假设您有以下<code>[urls]</code>行:</p> <pre> <code>/account/** = ssl, authc </code></pre> <p>该行指出:“以我的应用程序的路径的任何请求<code>/account</code>或任何它的子路径(<code>/account/foo</code>,<code>/account/bar/baz</code>,等),将触发”SSL,authc'过滤器链“。我们将在下面介绍过滤链。</p> <p>请注意,所有路径表达式都与应用程序的上下文根相关。这意味着如果你部署你的应用程序一天,<code>www.somehost.com/myapp</code>然后,然后将其部署到<code>www.anotherhost.com</code>(没有'myapp'子路径),模式匹配仍然会工作。所有路径都是相对于HttpServletRequest.getContextPath()值。</p>  <strong>订单事项!</strong> <hr /> <p>URL路径表达式根据传入请求按照它们定义的顺序和<em>第一个匹配的WINS</em>进行评估。例如,让我们假设有以下链定义:</p> <pre> <code>/account/** = ssl, authc /account/signup = anon </code></pre> <p>如果传入的请求打算到达<code>/account/signup/index.html</code>(所有“anon'ymous用户可访问),<em>它将永远不会被处理!</em>。原因是该<code>/account/**</code>模式首先匹配传入请求,并将所有剩余定义“短路”。</p> <p>始终记住根据<em>FIRST MATCH WINS</em>策略定义您的过滤器链!</p> <p> </p>   <h5>过滤器链定义</h5> <p>等号(=)右侧的令牌是以逗号分隔的过滤器列表,以对匹配该路径的请求执行。它必须匹配以下格式:</p> <pre> <code>filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN] </code></pre> <p>哪里:</p> <ul> <li><em>filterN</em>是在<code>[main]</code>部分中定义的过滤器bean的名称</li> <li><code>[optional_configN]</code>是一个可选的括号字符串,对于<em>该特定路径</em>(每个过滤器,<em>特定</em>于<em>路径的</em>配置!)<em>的特定</em>过滤器具有含义。如果过滤器不需要为该URL路径指定特定的配置,您可以丢弃括号,<code>filterN[]</code>只是变成了<code>filterN</code>。</li> </ul> <p>并且因为过滤器令牌定义链(aka List),记住顺序很重要!按您希望请求流过链的顺序定义逗号分隔的列表。</p> <p>最后,如果不满足其必要条件(例如,执行重定向,使用HTTP错误代码进行响应,直接呈现等),则每个过滤器都可以自由地处理响应。否则,预期允许请求通过链继续到最终目的地视图。</p>  <strong>小费</strong> <hr /> <p>能够对路径特定配置(即<code>[optional_configN]</code>过滤器令牌的一部分)做出反应是对于Shiro过滤器可用的独特特征。</p> <p>如果你想创建自己的<code>javax.servlet.Filter</code>实现,也可以这样做,请确保你的过滤器子类org.apache.shiro.web.filter.PathMatchingFilter</p> <p> </p>   <h6>可用过滤器</h6> <p>可用于过滤器链定义的“池”过滤器在此<code>[main]</code>部分中定义。在主节中分配给它们的名称是在过滤器链定义中使用的名称。例如:</p> <pre> <code>[main] ... myFilter = com.company.web.some.FilterImplementation myFilter.property1 = value1 ... [urls] ... /some/path/** = myFilter </code></pre> <h2>默认过滤器</h2> <p>当运行Web应用程序时,Shiro将创建一些有用的默认<code>Filter</code>实例,并使其在<code>[main]</code>部分自动可用。您可以像配置<code>main</code>其他bean一样配置它们,并在链定义中引用它们。例如:</p> <pre> <code>[main] ... # Notice how we didn't define the class for the FormAuthenticationFilter ('authc') - it is instantiated and available already: authc.loginUrl = /login.jsp ... [urls] ... # make sure the end-user is authenticated. If not, redirect to the 'authc.loginUrl' above, # and after successful authentication, redirect them back to the original account page they # were trying to view: /account/** = authc ... </code></pre> <p>自动可用的默认Filter实例由DefaultFilter枚举定义,枚举的<code>name</code>字段是可用于配置的名称。他们是:</p> <table> <thead> <tr> <th>过滤器名称</th> <th>类</th> </tr> </thead> <tbody> <tr> <td>anon</td> <td>org.apache.shiro.web.filter.authc.AnonymousFilter</td> </tr> <tr> <td>authc</td> <td>org.apache.shiro.web.filter.authc.FormAuthenticationFilter</td> </tr> <tr> <td>authcBasic</td> <td>org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter</td> </tr> <tr> <td>登出</td> <td>org.apache.shiro.web.filter.authc.LogoutFilter</td> </tr> <tr> <td>noSessionCreation</td> <td>org.apache.shiro.web.filter.session.NoSessionCreationFilter</td> </tr> <tr> <td>烫发</td> <td>org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter</td> </tr> <tr> <td>港口</td> <td>org.apache.shiro.web.filter.authz.PortFilter</td> </tr> <tr> <td>休息</td> <td>org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter</td> </tr> <tr> <td>角色</td> <td>org.apache.shiro.web.filter.authz.RolesAuthorizationFilter</td> </tr> <tr> <td>ssl</td> <td>org.apache.shiro.web.filter.authz.SslFilter</td> </tr> <tr> <td>用户</td> <td>org.apache.shiro.web.filter.authc.UserFilter</td> </tr> </tbody> </table> <h2>启用和禁用过滤器</h2> <p>与任何过滤器链定义机制(<code>web.xml</code>,Shiro的INI等)的情况一样,只需通过将过滤器包含在过滤器链定义中来启用它,并通过从链定义中删除它来禁用它。</p> <p>但是在Shiro 1.2中添加的一个新功能是能够启用或禁用过滤器,而不从过滤器链中删除它们。如果启用(默认设置),则会按预期对请求进行过滤。如果禁用,则过滤器将允许请求立即传递到中的下一个元素<code>FilterChain</code>。您可以通常基于配置属性触发过滤器的启用状态,或者甚至可以<em>根据请求</em>触发它。</p> <p>这是一个强大的概念,因为与更改静态过滤器链定义(这将是永久和不灵活的)相比,基于某些要求启用或禁用过滤器通常更方便。</p> <p>Shiro通过其OncePerRequestFilter抽象父类完成此操作。所有Shiro的开箱即用的Filter实现子类化这一个,因此可以启用或禁用,而不从过滤器链中删除它们。你可以为你自己的过滤器实现子类这个类,如果你也需要这个功能*。</p> <p>* SHIRO-224希望能为任何过滤器启用此功能,而不只是那些子类<code>OncePerRequestFilter</code>。如果这对您很重要,请投票支持此问题。</p> <h3>一般启用/禁用</h3> <p>该OncePerRequestFilter(及其所有子类)支持启用/所有的请求,以及在每个请求的基础禁用。</p> <p>对所有请求的一般启用或禁用过滤器是通过将其<code>enabled</code>属性设置为true或false来实现的。默认设置是<code>true</code>因为大多数过滤器本身需要执行,如果它们在链中配置。</p> <p>例如,在shiro.ini中:</p> <pre> <code>[main] ... # configure Shiro's default 'ssl' filter to be disabled while testing: ssl.enabled = false [urls] ... /some/path = ssl, authc /another/path = ssl, roles[admin] ... </code></pre> <p>此示例显示可能许多URL路径都可能要求请求必须由SSL连接保护。在开发过程中设置SSL可能会令人沮丧和耗时。在开发过程中,可以禁用ssl过滤器。部署到生产时,您可以使用一个配置属性启用它 - 这比手动更改所有URL路径或维护两个Shiro配置要容易得多。</p> <h3>请求特定的启用/禁用</h3> <p><code>OncePerRequestFilter</code>实际上根据其<code>isEnabled(request, response)</code>方法确定是否启用或禁用过滤器。</p> <p>此方法默认返回属性的值,<code>enabled</code>用于一般启用/禁用所有请求,如上所述。如果要根据<em>请求特定</em>条件启用或禁用过滤器,则可以覆盖该<code>OncePerRequestFilter</code><code>isEnabled(request,response)</code>方法以执行更具体的检查。</p> <h3>路径特定的启用/禁用</h3> <p>Shiro的PathMatchingFilter(一个子类)<code>OncePerRequestFilter</code>能够基于被过滤的<em>特定路径</em>对配置做出反应,这意味着除了传入的请求和响应之外,您还可以基于路径和特定于路径的配置启用或禁用过滤器。</p> <p>如果您需要能够对匹配路径和路径特定配置做出反应,以确定是启用还是禁用过滤器,而不是<code>OncePerRequestFilter</code> <code>isEnabled(request,response)</code>覆盖方法,那么您将覆盖该<code>PathMatchingFilter</code> <code>isEnabled(request,response,path,pathConfig)</code>方法。</p> <h2>会话管理</h2> <h3>Servlet容器会话</h3> <p>在Web环境中,Shiro的默认会话管理器<code>SessionManager</code>实现是<code>ServletContainerSessionManager</code>。这个非常简单的实现将所有会话管理职责(包括如果servlet容器支持它的会话群集)委托给运行时Servlet容器。它本质上是一个桥梁,用于Shiro的会话API到servlet容器,没有别的。</p> <p>使用此默认值的一个好处是,使用现有servlet容器会话配置(超时,任何容器特定的集群机制等)的应用程序将按预期工作。</p> <p>这个默认的缺点是,你绑定到servlet容器的特定会话行为。例如,如果您想集群会话,但在生产中使用Jetty进行测试和Tomcat,则容器特定的配置(或代码)将不可移植。</p> <h4>Servlet容器会话超时</h4> <p>如果使用默认的servlet容器支持,您可以在Web应用程序的<code>web.xml</code>文件中按预期配置会话超时。例如:</p> <pre> <code class="language-xml"><session-config> <!-- web.xml expects the session timeout in minutes: --> <session-timeout>30</session-timeout> </session-config> </code></pre> <h3>本地会话</h3> <p>如果希望您的会话配置设置和集群在servlet容器(例如Jetty在测试中,但是Tomcat或JBoss在生产中)是可移植的,或者您想要控制特定的会话/集群功能,您可以启用Shiro的本地会话管理。</p> <p>“Native”这个词意味着Shiro自己的企业会话管理实现将用于支持所有<code>Subject</code>和<code>HttpServletRequest</code>会话,并完全绕过servlet容器。但是放心 - Shiro直接实现了Servlet规范的相关部分,所以任何现有的web / http相关代码如预期一样工作,并且不需要“知道”Shiro是透明地管理会话。</p> <h4>DefaultWebSessionManager</h4> <p>要为Web应用程序启用本机会话管理,您需要配置一个本地的Web会话管理器来覆盖默认的基于servlet容器的会话管理器。你可以通过配置<code>DefaultWebSessionManager</code>Shiro的实例来实现<code>SecurityManager</code>。例如,在<code>shiro.ini</code>:</p> <p><strong>shiro.ini本地Web会话管理</strong></p> <pre> <code>[main] ... sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager # configure properties (like session timeout) here if desired # Use the configured native session manager: securityManager.sessionManager = $sessionManager </code></pre> <p>一旦声明,您可以配置<code>DefaultWebSessionManager</code>实例与本地会话选项,如会话超时和聚类配置,如会话管理部分所述。</p> <h5>本地会话超时</h5> <p>配置<code>DefaultWebSessionManager</code>实例后,按会话管理:会话超时中所述配置会话超时</p> <h5>会话Cookie</h5> <p>在<code>DefaultWebSessionManager</code>支持两个特定网络的配置属性:</p> <ul> <li><code>sessionIdCookieEnabled</code> (布尔)</li> <li><code>sessionIdCookie</code>,一个Cookie实例。</li> </ul>  <strong>Cookie作为模板</strong> <hr /> <p>该<code>sessionIdCookie</code>属性本质上是一个模板 - 您配置<code>Cookie</code>实例属性,并且此模板将用于在运行时使用适当的会话ID值设置实际的HTTP Cookie。</p>   <h6>会话Cookie配置</h6> <p>DefaultWebSessionManager的<code>sessionIdCookie</code>默认实例是a <code>SimpleCookie</code>。这个简单的实现允许对要在http Cookie上配置的所有相关属性进行JavaBeans风格的属性配置。</p> <p>例如,您可以设置Cookie域:</p> <pre> <code>[main] ... securityManager.sessionManager.sessionIdCookie.domain = foo.com </code></pre> <p>有关其他属性,请参阅SimpleCookie JavaDoc。</p> <p>cookie的默认名称<code>JSESSIONID</code>与servlet规范一致。此外,Shiro的cookie支持<code>HttpOnly</code>标志。该<code>sessionIdCookie</code>套<code>HttpOnly</code>到<code>true</code>默认情况下,额外的安全性。</p>  <strong>注意</strong> <hr /> <p>Shiro的<code>Cookie</code>概念<code>HttpOnly</code>甚至在Servlet 2.4和2.5环境中支持该标志(而Servlet API只在2.6或更高版本中支持它)。</p>   <h6>禁用会话Cookie</h6> <p>如果您不想使用会话Cookie,可以通过将<code>sessionIdCookieEnabled</code>属性配置为false 来禁用它们。例如:</p> <p><strong>禁用本机会话Cookie</strong></p> <pre> <code>[main] ... securityManager.sessionManager.sessionIdCookieEnabled = false </code></pre> <h2>记住我的服务</h2> <p>Shiro将执行'rememberMe'服务,如果<code>AuthenticationToken</code>实现的<code>org.apache.shiro.authc.RememberMeAuthenticationToken</code>接口。此接口指定一个方法:</p> <pre> <code>boolean isRememberMe(); </code></pre> <p>如果此方法返回<code>true</code>,则Shiro将记住会话中的最终用户身份。</p>  <strong>UsernamePasswordToken和RememberMe</strong> <hr /> <p>常用的<code>UsernamePasswordToken</code>已经实现了<code>RememberMeAuthenticationToken</code>接口并支持rememberMe登录。</p>   <h3>计划支持</h3> <p>要以程序方式使用rememberMe,可以将值设置为<code>true</code>支持此配置的类。例如,使用标准<code>UsernamePasswordToken</code>:</p> <pre> <code>UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(true); SecurityUtils.getSubject().login(token); ... </code></pre> <h3>基于表单的登录</h3> <p>对于Web应用程序,<code>authc</code>过滤器默认为a <code>FormAuthenticationFilter</code>。这支持将“rememberMe”布尔作为form / request参数读取。默认情况下,它期望请求参数被命名<code>rememberMe</code>。这里是一个示例shiro.ini配置支持这:</p> <pre> <code>[main] authc.loginUrl = /login.jsp [urls] # your login form page here: login.jsp = authc </code></pre> <p>在您的网络表单中,有一个名为“rememberMe”的复选框:</p> <pre> <code><form ...> Username: <input type="text" name="username"/> <br/> Password: <input type="password" name="password"/> ... <input type="checkbox" name="rememberMe" value="true"/>Remember Me? ... </form> </code></pre> <p>默认情况下,<code>FormAuthenticationFilter</code>会寻找名为请求参数<code>username</code>,<code>password</code>和<code>rememberMe</code>。如果这些不同于您在表单中使用的表单字段名称,则需要在上配置名称<code>FormAuthenticationFilter</code>。例如,在<code>shiro.ini</code>:</p> <pre> <code>[main] ... authc.loginUrl = /whatever.jsp authc.usernameParam = somethingOtherThanUsername authc.passwordParam = somethingOtherThanPassword authc.rememberMeParam = somethingOtherThanRememberMe ... </code></pre> <h3>Cookie配置</h3> <p>您可以<code>rememberMe</code>通过设置默认的{{RememberMeManager}}的各种cookie属性来配置cookie的功能。例如,在shiro.ini中:</p> <pre> <code>[main] ... securityManager.rememberMeManager.cookie.name = foo securityManager.rememberMeManager.cookie.maxAge = blah ... </code></pre> <p>请参阅<code>CookieRememberMeManager</code>和支持<code>SimpleCookie</code>JavaDoc的配置属性。</p> <h3>自定义 <code>RememberMeManager</code></h3> <p>应该注意的是,如果默认的基于cookie的实现<code>RememberMeManager</code>不能满足你的需要,你可以插入任何你喜欢的<code>securityManager</code>类似,你将配置任何其他对象引用:</p> <pre> <code>[main] ... rememberMeManager = com.my.impl.RememberMeManager securityManager.rememberMeManager = $rememberMeManager </code></pre> <h2>JSP / GSP标签库</h2> <p>Apache Shiro提供了一个<code>Subject</code>-aware JSP / GSP标签库,允许您根据当前主题的状态控制您的JSP,JSTL或GSP页面输出。这对于基于查看网页的当前用户的身份和授权状态来个性化视图是非常有用的。</p> <h3>标签库配置</h3> <p>标签库描述符(TLD)文件捆绑在<code>shiro-web.jar</code>中<code>META-INF/shiro.tld</code>文件。要使用任何标记,请将以下行添加到JSP页面的顶部(或在您定义页面指令的任何位置):</p> <pre> <code><%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> </code></pre> <p>我们使用<code>shiro</code>前缀来指示shiro标签库命名空间,但是您可以分配任何您喜欢的名称。</p> <p>现在,我们将介绍每个标记,并说明如何使用它来呈现网页。</p> <h3>该<code>guest</code>标签</h3> <p><code>guest</code>只有当前<code>Subject</code>被视为“访客”时,标记才会显示其包装内容。客人是<code>Subject</code>没有身份的任何人。也就是说,我们不知道用户是谁,因为他们没有登录,他们不记得(从记住我服务)从以前的网站访问。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a> today! </shiro:guest> </code></pre> <p>该<code>guest</code>标签是的逻辑相反的<code>user</code>标记。</p> <h3>该<code>user</code>标签</h3> <p><code>user</code>仅当当前<code>Subject</code>被视为“用户”时,标记才会显示其包装内容。在此上下文中的“用户”被定义为<code>Subject</code>具有已知标识,或者来自成功认证或者来自“记住我”服务。请注意,此标记在语义上与已认证的标记不同,后者比此标记更受限制。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:user> Welcome back John! Not John? Click <a href="login.jsp">here<a> to login. </shiro:user> </code></pre> <p>该<code>user</code>标签是的逻辑相反的<code>guest</code>标记。</p> <h3>该<code>authenticated</code>标签</h3> <p>仅当当前用户<em>在其当前会话期间</em>已成功通过身份验证<em>时,</em>才显示主体内容。它比“用户”标签限制性更强。它在逻辑上与“notAuthenticated”标签相反。</p> <p>该<code>authenticated</code>标签将显示其包裹内容仅在当前<code>Subject</code>已成功验证<em>了当前会话中</em>。这是一个比用户更严格的标签,用于保证敏感工作流中的身份。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:authenticated> <a href="updateAccount.jsp">Update your contact information</a>. </shiro:authenticated> </code></pre> <p>该<code>authenticated</code>标签是的逻辑相反的<code>notAuthenticated</code>标记。</p> <h3>该<code>notAuthenticated</code>标签</h3> <p>该<code>notAuthenticated</code>如果当前标签将显示其包裹内容<code>Subject</code>已<strong>不</strong>还成功地在本届会议期间进行身份验证。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:notAuthenticated> Please <a href="login.jsp">login</a> in order to update your credit card information. </shiro:notAuthenticated> </code></pre> <p>该<code>notAuthenticated</code>标签是的逻辑相反的<code>authenticated</code>标记。</p> <h3>该<code>principal</code>标签</h3> <p>该<code>principal</code>标签将输出主题的<code>principal</code>(标识属性)或本金的属性。</p> <p>没有任何标记属性,标记将呈现<code>toString()</code>主体的值。例如(假设主体是字符串用户名):</p> <pre> <code class="language-xml">Hello, <shiro:principal/>, how are you today? </code></pre> <p>这是(大多数)等价于以下:</p> <pre> <code>Hello, <%= SecurityUtils.getSubject().getPrincipal().toString() %>, how are you today? </code></pre> <h4>类型主体</h4> <p>该<code>principal</code>标签假设默认情况下,该校长印的是<code>subject.getPrincipal()</code>价值。但是如果要打印一个<em>不是</em>主要主体的值,而是在主体的{ principal集合中另一个值,那么您可以通过类型获取该主体,并打印该值。</p> <p>例如,打印主题的用户ID(而不是用户名),假设ID在主体集合中:</p> <pre> <code>User ID: <principal type="java.lang.Integer"/> </code></pre> <p>这是(大多数)等价于以下:</p> <pre> <code>User ID: <%= SecurityUtils.getSubject().getPrincipals().oneByType(Integer.class).toString() %> </code></pre> <h4>主要财产</h4> <p>但是如果主体(上面的默认主要主体或者“类型化”主体)是一个复杂对象而不是一个简单的字符串,并且你想引用该主体上的一个属性呢?您可以使用该<code>property</code>属性指示要读取的属性的名称(必须通过JavaBeans兼容的getter方法访问)。例如(假设主要主体是User对象):</p> <pre> <code>Hello, <shiro:principal property="firstName"/>, how are you today? </code></pre> <p>这是(大多数)等价于以下:</p> <pre> <code>Hello, <%= SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>, how are you today? </code></pre> <p>或者,结合type属性:</p> <pre> <code>Hello, <shiro:principal type="com.foo.User" property="firstName"/>, how are you today? </code></pre> <p>这在很大程度上等同于以下:</p> <pre> <code>Hello, <%= SecurityUtils.getSubject().getPrincipals().oneByType(com.foo.User.class).getFirstName().toString() %>, how are you today? </code></pre> <h3>该<code>hasRole</code>标签</h3> <p><code>hasRole</code>仅当当前<code>Subject</code>分配了指定的角色时,标记才会显示其包装内容。</p> <p>例如:</p> <pre> <code><shiro:hasRole name="administrator"> <a href="admin.jsp">Administer the system</a> </shiro:hasRole> </code></pre> <p>该<code>hasRole</code>标签是的逻辑相反lacksRole标记。</p> <h3>该<code>lacksRole</code>标签</h3> <p><code>lacksRole</code>仅当当前<code>Subject</code> <strong>未</strong>分配指定角色时,标记才会显示其包装内容。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:lacksRole name="administrator"> Sorry, you are not allowed to administer the system. </shiro:lacksRole> </code></pre> <p>该<code>lacksRole</code>标签是的逻辑相反hasRole标记。</p> <h3>该<code>hasAnyRole</code>标签</h3> <p>该<code>hasAnyRole</code>如果当前标签将显示其包裹内容<code>Subject</code>被分配<em>任何</em>指定的角色从一个逗号分隔的角色名称列表。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:hasAnyRoles name="developer, project manager, administrator"> You are either a developer, project manager, or administrator. </shiro:lacksRole> </code></pre> <p>该<code>hasAnyRole</code>标签目前还没有一个逻辑相反的标记。</p> <h3>该<code>hasPermission</code>标签</h3> <p><code>hasPermission</code>只有当前的<code>Subject</code>“has”(暗示)指定的权限,标签才会显示其包装的内容。也就是说,用户具有指定的能力。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:hasPermission name="user:create"> <a href="createUser.jsp">Create a new User</a> </shiro:hasPermission> </code></pre> <p>该<code>hasPermission</code>标签是的逻辑相反lacksPermission标记。</p> <h3>该<code>lacksPermission</code>标签</h3> <p><code>lacksPermission</code>只有当前的<code>Subject</code> <strong>DOES没有</strong>(暗示)指定的权限,标签才会显示其包装的内容。也就是说,用户<strong>没有</strong>指定的能力。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:lacksPermission name="user:delete"> Sorry, you are not allowed to delete user accounts. </shiro:hasPermission> </code></pre> <p>该<code>lacksPermission</code>标签是的逻辑相反hasPermission标记。</p> <h2>借给文档</h2> <p>虽然我们希望本文档帮助您与Apache Shiro正在进行的工作,社区正在不断改进和扩展文档。如果您希望帮助Shiro项目,请考虑更正,扩展或添加您需要的文档。你提供的每一点帮助扩大了社区,反过来又改善了Shiro。</p> <p>提交文档的最简单方法是通过点击以下<code>Edit</code>链接提交拉取请求,将其发送到用户论坛或用户邮件列表。</p>
  • spring boot 入门 spring.profiles.active来分区配置

    spring boot 入门 使用spring.profiles.active来分区配置,,在spring boot中可以存在多个环境的配置文件通过配置spring.profiles.active来区分具体使用那个,也可以通过命令来指定使用那个。<h2>spring boot 入门 使用spring.profiles.active来分区配置</h2> <p>很多时候,我们项目在开发环境和生成环境的环境配置是不一样的,例如,数据库配置,在开发的时候,我们一般用测试数据库,而在生产环境的时候,我们是用正式的数据,这时候,我们可以利用profile在不同的环境下配置用不同的配置文件或者不同的配置</p> <p>spring boot允许你通过命名约定按照一定的格式(application-{profile}.properties)来定义多个配置文件,然后通过在application.properyies通过spring.profiles.active来具体激活一个或者多个配置文件,如果没有没有指定任何profile的配置文件的话,spring boot默认会启动application-default.properties。</p> <p>profile的配置文件可以按照application.properyies的放置位置一样,放于以下四个位置,</p> <ol> <li>当前目录的 “/config”的子目录下</li> <li>当前目录下</li> <li>classpath根目录的“/config”包下</li> <li>classpath的根目录下</li> </ol> <p>在这里我们就定义俩个profile文件,application-cus1.properties和application-cus2.properties,并在俩个文件中都分别写上变量cusvar=cus1和cusvar=cus2</p> <br /> 我们在application.properyies也写上,并把profile切换到application-cus1.properties的配置文件 <pre> <code>cusvar=cus3 spring.profiles.active=cus1</code></pre> 可以通过这样子来测试 <pre> <code class="language-java"> @RestController @RequestMapping("/task") public class TaskController { @RequestMapping(value = {"/",""}) public String hellTask(@Value("${cusvar}")String cusvar ){ return "hello task !! myage is " + cusvar; } }</code></pre> <p>在这里可以看到spring.profiles.active激活的profile不同,打印出来的结果也不一样。</p> <p>除了可以用profile的配置文件来分区配置我们的环境变量,在代码里,我们还可以直接用@Profile注解来进行配置,例如数据库配置,这里我们先定义一个接口<br />  </p> <pre> <code class="language-java">public interface DBConnector { public void configure(); }</code></pre> <br /> 分别定义俩个实现类来实现它 <pre> <code class="language-java">/** * 测试数据库 */ @Component @Profile("testdb") public class TestDBConnector implements DBConnector { @Override public void configure() { System.out.println("testdb"); } } /** * 生产数据库 */ @Component @Profile("devdb") public class DevDBConnector implements DBConnector { @Override public void configure() { System.out.println("devdb"); } }</code></pre> 通过在配置文件激活具体使用哪个实现类 <pre> <code>spring.profiles.active=testdb</code></pre> 然后就可以这么用了 <pre> <code class="language-java">@RestController @RequestMapping("/task") public class TaskController { @Autowired DBConnector connector ; @RequestMapping(value = {"/",""}) public String hellTask(){ connector.configure(); //最终打印testdb return "hello task !! myage is " + myage; } }</code></pre> 除了spring.profiles.active来激活一个或者多个profile之外,还可以用spring.profiles.include来叠加profile <pre> <code>spring.profiles: testdb spring.profiles.include: proddb,prodmq</code></pre> 以上就是spring boot用profile的作用 <h2>通过命令行设置属性值</h2> <p>相信使用过一段时间Spring Boot的用户,一定知道这条命令:<code>java -jar xxx.jar --server.port=8888</code>,通过使用--server.port属性来设置xxx.jar应用的端口为8888。</p> <p>在命令行运行时,连续的两个减号<code>--</code>就是对<code>application.properties</code>中的属性值进行赋值的标识。所以,<code>java -jar xxx.jar --server.port=8888</code>命令,等价于我们在<code>application.properties</code>中添加属性<code>server.port=8888</code>,该设置在样例工程中可见,读者可通过删除该值或使用命令行来设置该值来验证。</p> <p>通过命令行来修改属性值固然提供了不错的便利性,但是通过命令行就能更改应用运行的参数,那岂不是很不安全?是的,所以Spring Boot也贴心的提供了屏蔽命令行访问属性的设置,只需要这句设置就能屏蔽:<code>SpringApplication.setAddCommandLineProperties(false)</code>。</p> <h2>多环境配置</h2>     以上都不是重点,这才是重点,这才是重点,这才是重点,重要的事情说3遍。我们在开发Spring Boot应用时,通常同一套程序会被应用和安装到几个不同的环境,比如:开发、测试、生产等。其中每个环境的数据库地址、服务器端口等等配置都会不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常繁琐且容易发生错误的事。<br />     对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,Spring Boot也不例外,或者说更加简单。<br />        在Spring Boot中多环境配置文件名需要满足<code>application-{profile}.properties</code>的格式,其中<code>{profile}</code>对应你的环境标识,比如:<br />    application-dev.properties:开发环境<br />    application-test.properties:测试环境<br />    application-prod.properties:生产环境<br />        至于哪个具体的配置文件会被加载,需要在<code>application.properties</code>文件中通过<code>spring.profiles.active</code>属性来设置,其值对应<code>{profile}</code>值。<br /> 如:<code>spring.profiles.active=test</code>就会加载<code>application-test.properties</code>配置文件内容<br /> 下面,以不同环境配置不同的服务端口为例,进行样例实验。<br />  <br />      针对各环境新建不同的配置文件<code>application-dev.properties</code>、<code>application-test.properties</code>、<code>application-prod.properties</code><br />      在这三个文件均都设置不同的<code>server.port</code>属性,如:dev环境设置为8080,test环境设置为9090,prod环境设置为80<br />      application.properties中设置<code>spring.profiles.active=dev</code>,就是说默认以dev环境设置<br />  <br /> 测试不同配置的加载:<br />      执行java -jar xxx.jar,可以观察到服务端口被设置为8080,也就是默认的开发环境(dev)<br />  <br />      执行java -jar xxx.jar --spring.profiles.active=test,可以观察到服务端口被设置为9090,也就是测试环境的配置(test)<br />  <br />      执行java -jar xxx.jar --spring.profiles.active=prod,可以观察到服务端口被设置为80,也就是生产环境的配置(prod)<br />  <br /> 按照上面的实验,可以如下总结多环境的配置思路:<br />    application.properties中配置通用内容,并设置spring.profiles.active=dev,以开发环境为默认配置<br />    application-{profile}.properties中配置各个环境不同的内容<br /> 通过命令行方式去激活不同环境的配置。<br /> <br /> <strong>多环境高级应用</strong><br />        在某些情况下,应用的某些业务逻辑可能需要有不同的实现。例如邮件服务,假设<code>EmailService</code>中包含的<code>send(String email)</code>方法向指定地址发送电子邮件,但是我们仅仅希望在生产环境中才执行真正发送邮件的代码,而开发环境里则不发送以免向用户发送无意义的垃圾邮件。<br />        我们可以借助Spring的注解<code>@Profile</code>实现这样的功能,这样需要定义两个实现<code>EmailService</code>借口的类: <pre> <code class="language-java">/**  * 发送邮件接口.  */ public interface EmailService {     /**发送邮件*/     publicvoid send(); } 发送邮件的具体实现(dev-开发环境的代码): @Service @Profile("dev") //开发环境的时候. public class DevEmailServiceImpl implements EmailService{       @Override     publicvoid send() {        System.out.println("DevEmailServiceImpl.send().开发环境不执行邮件的发送.");     } }</code></pre> <br />  <br />  <br /> 发送邮件的具体实现(prod-生产环境的代码): <pre> <code class="language-java">@Service @Profile("prod") //生产环境. public class ProdEmailServiceImpl2 implements EmailService{         @Override     publicvoid send() {        System.out.println("DevEmailServiceImpl.send().生产环境执行邮件的发送.");        //具体的邮件发送代码.        //mail.send();     } }  </code></pre> <br /> <code>@Profile("dev")</code>表明只有Spring定义的Profile为<code>dev</code>时才会实例化<code>DevEmailService</code>这个类。那么如何设置Profile呢? <h3>在配置文件中指定</h3>  <br /> 在<code>application.properties</code>中加入: <div> <pre> <code>spring.profiles.active=dev</code></pre> </div>   <h3>通过命令行参数</h3> <div> <pre> <code>java -jar app.jar --spring.profiles.active=dev</code></pre> </div>  
  • centos6源码安装MySQL5.6

    centos6源码安装MySQL5.6<p>1.安装mysql5.6依存包</p> <p>2.下载编译包</p> <pre> <code class="language-html">wget https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz tar xvf mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz</code></pre> <p>3.复制到指定目录</p> <pre> <code class="language-html">mv mysql-5.6.35-linux-glibc2.5-x86_64 /usr/local/mysql</code></pre> <p>4.创建mysql用户和组</p> <pre> <code class="language-html">groupadd mysql useradd -r -g mysql mysql -d /usr/local/mysql passwd mysql</code></pre> <p>5.修改目录权限</p> <pre> <code class="language-html">chown -R mysql:mysql /usr/local/mysql</code></pre> <p>6.安装数据库</p> <pre> <code class="language-html">su mysql /usr/local/mysql/scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data exit</code></pre> <p>7.复制mysql配置文件</p> <pre> <code class="language-html">cd /usr/local/mysql/support-files cp my-default.cnf /etc/my.cnf</code></pre> <p>8.添加系统服务</p> <pre> <code class="language-html">cp mysql.server /etc/init.d/mysql chkconfig mysql on</code></pre> <p>9.添加环境变量</p> <pre> <code class="language-html">vim /etc/profile</code></pre> <p>在最下面添加</p> <pre> <code class="language-html">export MYSQL_HOME="/usr/local/mysql" export PATH="$PATH:$MYSQL_HOME/bin"</code></pre> <p>保存退出后,执行生效代码</p> <pre> <code class="language-html">. /etc/profile</code></pre> <p>10.启动mysql</p> <pre> <code class="language-html">service mysql start</code></pre> <p>11.设置root密码</p> <pre> <code class="language-html">mysqladmin -u root password '123456'</code></pre> <p>12.设置默认端口</p> <pre> <code class="language-html">vim /etc/my.cnf</code></pre> <pre> <code class="language-html">[mysqld] basedir=/usr/local/mysql datadir=/usr/local/mysql/data port=3306 server_id=1 socket=/tmp/mysql.sock</code></pre> <p>13.允许所有外部链接访问(可选)</p> <pre> <code class="language-html">mysql -u root -p</code></pre> <p>mysql命令行输入</p> <pre> <code class="language-html">GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION; FLUSH PRIVILEGES;</code></pre> <br /> <br /> 相关文档:<br /> <a rel="" target="_blank"href="http://www.leftso.com/blog/224.html" rel="" target="_blank">yum安装MySQL数据库</a><br />  
  • Apache配置worker模式_Apache配置worker模式调优

    Apache配置worker模式_Apache配置worker模式调优1.通过yum安装Apache(安装版本为:2.4.6)<br /> <br /> 2.查看默认的工作模式 <pre> <code class="language-html">[root@VM_26_67_centos ~]# httpd -V AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1. Set the 'ServerName' directive globally to suppress this message Server version: Apache/2.4.6 (CentOS) Server built:   Oct 19 2017 20:39:16 Server's Module Magic Number: 20120211:24 Server loaded:  APR 1.4.8, APR-UTIL 1.5.2 Compiled using: APR 1.4.8, APR-UTIL 1.5.2 Architecture:   64-bit Server MPM:     prefork   threaded:     no     forked:     yes (variable process count) Server compiled with....  -D APR_HAS_SENDFILE  -D APR_HAS_MMAP  -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)  -D APR_USE_SYSVSEM_SERIALIZE  -D APR_USE_PTHREAD_SERIALIZE  -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT  -D APR_HAS_OTHER_CHILD  -D AP_HAVE_RELIABLE_PIPED_LOGS  -D DYNAMIC_MODULE_LIMIT=256  -D HTTPD_ROOT="/etc/httpd"  -D SUEXEC_BIN="/usr/sbin/suexec"  -D DEFAULT_PIDLOG="/run/httpd/httpd.pid"  -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"  -D DEFAULT_ERRORLOG="logs/error_log"  -D AP_TYPES_CONFIG_FILE="conf/mime.types</code></pre> <br /> 3.切换到Apache的配置目录 <pre> <code class="language-html">#cd /etc/httpd/</code></pre> <br /> 这里有三个配置目录<br /> conf->核心配置<br /> conf.d->在conf中的http.conf会引入该目录下的其他配置<br /> conf.modules.d->Apache 的模块配置<br /> <br /> 还有个模块的安装存放目录的链接目录<br /> modules->模块存放目录<br /> 查看安装的工作模式模块 <pre> <code class="language-html">[root@VM_26_67_centos httpd]# ll modules/mod_mpm_* -rwxr-xr-x 1 root root 61040 Oct 20 04:40 modules/mod_mpm_event.so -rwxr-xr-x 1 root root 31872 Oct 20 04:40 modules/mod_mpm_prefork.so -rwxr-xr-x 1 root root 48520 Oct 20 04:40 modules/mod_mpm_worker.so [root@VM_26_67_centos httpd]# </code></pre> <br /> 4.修改加载模式模块<br /> 切换至模块加载配置文件目录 <pre> <code class="language-html">[root@VM_26_67_centos httpd]# cd /etc/httpd/conf.modules.d/ [root@VM_26_67_centos conf.modules.d]# ls 00-base.conf  00-dav.conf  00-lua.conf  00-mpm.conf  00-proxy.conf  00-systemd.conf  01-cgi.conf  10-php.conf [root@VM_26_67_centos conf.modules.d]#</code></pre> <br /> 修改配置文件00-mpm.conf <pre> <code class="language-bash"># Select the MPM module which should be used by uncommenting exactly # one of the following LoadModule lines: # prefork MPM: Implements a non-threaded, pre-forking web server # See: http://httpd.apache.org/docs/2.4/mod/prefork.html #注释默认工作模式模块 #LoadModule mpm_prefork_module modules/mod_mpm_prefork.so # worker MPM: Multi-Processing Module implementing a hybrid # multi-threaded multi-process web server # See: http://httpd.apache.org/docs/2.4/mod/worker.html #启用worker工作模式模块 LoadModule mpm_worker_module modules/mod_mpm_worker.so # event MPM: A variant of the worker MPM with the goal of consuming # threads only for connections with active processing # See: http://httpd.apache.org/docs/2.4/mod/event.html # #LoadModule mpm_event_module modules/mod_mpm_event.so </code></pre> <br /> <br /> 5.在Apache得核心配置文件httpd.conf中添加以下配置模块 <pre> <code class="language-html">cd /etc/httpd/conf <IfModule mpm_worker_module>     StartServers             3     MinSpareThreads         75     MaxSpareThreads        250     ThreadsPerChild         25     MaxRequestWorkers      400     MaxConnectionsPerChild   0 </IfModule></code></pre> 建议配置: <pre> <code class="language-bash">#mpm_worker模块 <IfModule mpm_worker_module> StartServers 2 #推荐设置:小=默认 中=3~5 大=5~10 MaxClients 150 #推荐设置:小=500 中=500~1500 大型=1500~3000 MinSpareThreads 25 #推荐设置:小=默认 中=50~100 大=100~200 MaxSpareThreads 75 #推荐设置:小=默认 中=80~160 大=200~400 ThreadsPerChild 25 #推荐设置:小=默认 中=50~100 大型=100~200 MaxRequestsPerChild 0 #推荐设置:小=10000 中或大=10000~50000 #(此外,如果MaxClients/ThreadsPerChild大于16,还需额外设置ServerLimit参数,ServerLimit必须大于等于 MaxClients/ThreadsPerChild 的值。) </IfModule></code></pre> <p>对应的配置参数作用如下:</p> StartServers<br /> 启动Apache时创建的子进程数。<br /> MinSpareServers<br /> 处于空闲状态的最小子进程数。 <p>所谓空闲子进程是指没有正在处理请求的子进程。如果当前空闲子进程数少于<code>MinSpareServers</code>,那么Apache将以最大每秒一个的速度产生新的子进程。只有在非常繁忙机器上才需要调整这个参数。此值不宜过大。</p> <br /> MaxSpareServers<br /> 处于空闲状态的最大子进程数。 <p>只有在非常繁忙机器上才需要调整这个参数。此值不宜过大。如果你将该指令的值设置为比<code>MinSpareServers</code>小,Apache将会自动将其修改成<code>MinSpareServers+1</code>。</p> <br /> MaxClients<br /> 允许同时连接的最大请求数量。 <p>任何超过<code>MaxClients</code>限制的请求都将进入等待队列,直到达到<code>ListenBacklog</code>指令限制的最大值为止。</p> <p>对于非线程型的MPM(也就是<code>mpm_prefork</code>),<code>MaxClients</code>表示可以用于处理客户端请求的最大子进程数量,默认值是256。要增大这个值,你必须同时增大<code>ServerLimit</code>。</p> <p>对于线程型或者混合型的MPM(也就是<code>mpm_beos</code>或<code>mpm_worker</code>),<code>MaxClients</code>表示可以用于处理客户端请求的最大线程数量。线程型的<code>mpm_beos</code>的默认值是50。对于混合型的MPM默认值是16(<code>ServerLimit</code>)乘以25(<code>ThreadsPerChild</code>)的结果。因此要将<code>MaxClients</code>增加到超过16个进程才能提供的时候,你必须同时增加<code>ServerLimit</code>的值。</p> <br /> MinSpareThreads<br /> 处于空闲状态的最小线程数。 <p>不同的MPM对这个指令的处理是不一样的:</p> <p><code>mpm_worker</code>的默认值是75。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太少,子进程将产生新的空闲线程。<code>mpm_netware</code>的默认值是10。既然这个MPM只运行单独一个子进程,此MPM当然亦基于整个服务器监视空闲线程数。<code>mpm_beos</code>和<code>mpmt_os2</code>的工作方式与<code>mpm_netware</code>差不多,<code>mpm_beos</code>的默认值是1;<code>mpmt_os2</code>的默认值是5。</p> <br /> MaxSpareThreads<br /> 处于空闲状态的最大线程数。 <p>不同的MPM对这个指令的处理是不一样的:</p> <p><code>mpm_worker</code>的默认值是250。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太多,子进程将杀死多余的空闲线程。<code>mpm_netware</code>的默认值是100。既然这个MPM只运行单独一个子进程,此MPM当然亦基于整个服务器监视空闲线程数。<code>mpm_beos</code>和<code>mpmt_os2</code>的工作方式与<code>mpm_netware</code>差不多,<code>mpm_beos</code>的默认值是50;<code>mpmt_os2</code>的默认值是10。</p> <p><em>备注</em>:<code>ServerLimit</code>表示Apache允许创建的最大进程数。 值得注意的是,Apache在编译时内部有一个硬限制<code>ServerLimit 20000</code>(对于<code>mpm_prefork</code>模块为<code>ServerLimit 200000</code>)。你不能超越这个限制。<br /> 使用这个指令时要特别当心。如果将<code>ServerLimit</code>设置成一个高出实际需要许多的值,将会有过多的共享内存被分配。如果将<code>ServerLimit</code>和<code>MaxClients</code>设置成超过系统的处理能力,Apache可能无法启动,或者系统将变得不稳定。</p> <p><em>注意</em>:在配置相关参数时,请先保证服务器具备<em>足够</em>的硬件性能(例如:CPU、内存等)。 如果发现自启动后,随着服务器的运行时间增加,服务器的内存占用也随之增加,可能是程序中出现内存泄露,请向下调整参数<code>MaxRequestsPerChild</code>的值以降低内存泄露带来的影响,然后尽快找出程序中的问题之所在。</p> <br /> 6.重启httpd服务 <pre> <code class="language-html">[root@VM_26_67_centos httpd]# service httpd restart</code></pre> <br /> <br /> 7.查看工作模式 <pre> <code class="language-html">[root@VM_26_67_centos httpd]# httpd -V AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1. Set the 'ServerName' directive globally to suppress this message Server version: Apache/2.4.6 (CentOS) Server built:   Oct 19 2017 20:39:16 Server's Module Magic Number: 20120211:24 Server loaded:  APR 1.4.8, APR-UTIL 1.5.2 Compiled using: APR 1.4.8, APR-UTIL 1.5.2 Architecture:   64-bit Server MPM:     worker   threaded:     yes (fixed thread count)     forked:     yes (variable process count) Server compiled with....  -D APR_HAS_SENDFILE  -D APR_HAS_MMAP  -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)  -D APR_USE_SYSVSEM_SERIALIZE  -D APR_USE_PTHREAD_SERIALIZE  -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT  -D APR_HAS_OTHER_CHILD  -D AP_HAVE_RELIABLE_PIPED_LOGS  -D DYNAMIC_MODULE_LIMIT=256  -D HTTPD_ROOT="/etc/httpd"  -D SUEXEC_BIN="/usr/sbin/suexec"  -D DEFAULT_PIDLOG="/run/httpd/httpd.pid"  -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"  -D DEFAULT_ERRORLOG="logs/error_log"  -D AP_TYPES_CONFIG_FILE="conf/mime.types"  -D SERVER_CONFIG_FILE="conf/httpd.conf"</code></pre>