SpringBoot ---- Spring Security

Spring Security

介绍

Spring Security是Spring提供的一个安全框架,提供用户认证用户授权功能,最主要的是它提供了简单的使用方式,同时又有很高的灵活性,简单,灵活,强大。

入门

配置类详解

@Override
protected void configure(HttpSecurity http) throws Exception { // 配置授权等
    http.rememberMe()            // 开启 rememberMe 功能
        	    .rememberMeParameter("remember")   // 指定传递的参数名(需与前端一致)
                .rememberMeCookieName("remember")  // 指定返回 cookie 中参数的名字
                .tokenValiditySeconds(2*24*60*60)  // 令牌有效时间
        // 前端可直接使用 checkbox name="remember-me" + submit 按钮
        // 如果是使用 ajax 需自行判断并添加 "remember-me" 参数
        .and().csrf().disable()  // 禁用跨站请求伪造防御
        // 会拦截所有 POST 请求,检查是否携带 token,自带登录无影响
        //.and().authorizeRequests()  // 动态鉴权
        //    .antMatchers("/login").permitAll()
        //    .antMatchers("/index").authenticated()
        //    .anyRequest().access(
        //		"@authorizeServiceImpl.hasPermission(request, authentication)")
        .authorizeRequests()  // 授权配置
            .antMatchers("/login", "/login.html")  // 包含页面,后跟授权
                .permitAll()                       // 授权:所有人可访问
            .antMatchers("/login", "/login.html")
                .hasAnyAuthority("ROLE_admin", "login")  // 权限形式授权
                // .hasAnyRole("admin", "user")              // 角色形式授权(二选一即可)
                // .hasRole("admin").hasAuthority            // 单角色/权限形式
        		// hasAuthority("ROLE_admin") 等价于 hasRole("admin")
                // 若 hasAuthority("xxx") 则表示指定权限名为 xxx
                // 下方授权可以访问上方授权页面,反之不能
            .anyRequest()  // 任意请求(不包括上面的)
                .authenticated()  // 登录可访问
        
        // .and()                         // 模式配置
        // .formLogin()                   // 表单模式
        // .loginPage("/login.html")      // 登录页面
        // .loginProcessingUrl("/login")  // 拦截 URL
        // .defaultSuccessUrl("/index")   // 登录跳转 URL
        // .failureUrl("/login");         // 失败跳转 URL
        .and().httpBasic()  // HttpBasic 模式
        
        .and().sessionManagement()                                     // seesion 管理
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)  // session 创建策略
            .invalidSessionUrl("/login")                               // 超时跳转 URL
            .sessionFixation().none()                                  // 配置 session 保护
            .maximumSessions(1)                                        // 最大 session 连接数
            // true:不可再次登录;false:之前登录的 session 会下线
            .maxSessionsPreventsLogin(false)
            .expiredSessionStrategy(new SessionExpiredStrategy());     // 超时配置
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 用户权限配置
    auth.inMemoryAuthentication()                      // 从内存中读取角色权限
            .withUser("user")                              // 配置用户名
            .password(passwordEncoder().encode("123456"))  // 配置密码
            .roles("user")                                 // 配置角色
            // .authorities("xxx")  // 指定权限,角色和权限都可以配置多个
            .and()
            .withUser("admin")
            .password(passwordEncoder().encode("123456"))
            .roles("admin")
            .and()
            .passwordEncoder(passwordEncoder());           // 配置加密方式
    // 从数据库动态加载配置,需要配置好相关文件
    // auth.userDetailsService(userService)
        // .passwordEncoder(passwordEncoder());
}

@Override
public void configure(WebSecurity web) throws Exception {  // 静态资源配置(不拦截)
    web.ignoring()
        .antMatchers("/css/*", "/fonts/*", "/js/*", "/img/*");
}
@Bean
public PasswordEncoder passwordEncoder() {  // 密码加密方式
    return new BCryptPasswordEncoder();
}

登录

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

编写测试接口

@RestController
public class UserController {

//    @GetMapping("login")
//    public String hello() {
//        return "login";
//    }

    @GetMapping("index")
    public String index() {
        return "index";
    }
    @GetMapping("user")
    public String user() {
        return "user";
    }

    @GetMapping("admin")
    public String admin() {
        return "admin";
    }
}

基于 HttpBasic 模式的登录认证

// SecurityConfig.java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()                           // 授权配置
                .anyRequest()                              // 任意请求
                .authenticated()                           // 登录可访问
                .and()
                .httpBasic();                              // 使用 HttpBasic 模式
    }
}

访问 http://localhost:8080/hello 查看效果

自定义用户名密码
spring:
  security:
    user:
      name: admin       # 自定义用户名
      password: 123456  # 自定义密码

HttpBasic 模式会将密码进行进行 base64 后放在 header 中传输给服务器,安全性差

基于 FormLogin 表单模式的登录认证

三要素
  • 登录认证逻辑
  • 资源访问控制
  • 用户角色权限
编写配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/user").hasAnyRole("user")
                .antMatchers("/admin").hasAnyAuthority("ROLE_admin")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                // tips: 默认 processingUrl = loginPage = /login
                // 若只配置 page, processingUrl 也会默认配置为 page而非 /login
                // .loginPage("/login.html")      // 登录页面
                // .loginProcessingUrl("/login")  // 拦截 URL
                .defaultSuccessUrl("/index")      // 登录跳转 URL
                .failureUrl("/login.html");       // 失败跳转 URL

    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()  // 从内存中读取角色权限
                .withUser("user")
                .password(passwordEncoder().encode("123"))
                .roles("user")
                .and()
                .withUser("admin")
                    .password(passwordEncoder().encode("123"))
                    .roles("admin")
                .and()
                .passwordEncoder(passwordEncoder());  // 加密方式
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/css/*", "/fonts/*", "/js/*", "/img/*");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

登录成功与失败

统一返回结果
// Result.java
@Data
public class Result {

    public interface ResultCode {
        Integer SUCCESS = 20000;
        Integer ERROR = 20001;
        Integer TIMEOUT = 20002;
    }

    private Boolean success;

    private Integer code;

    private String message;

    private Map<String, Object> data = new HashMap<String, Object>();
    private Result(){}  // 私有化,让此类只能使用 success(), error()(限定状态)
    public static Result success(){
        Result r = new Result();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }

    public static Result error(){
        Result r = new Result();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    // return this 可以让方法返回本身,进行链式编程
    public Result success(Boolean success){
        this.setSuccess(success);
        return this;
    }
    public Result message(String message){
        this.setMessage(message);
        return this;
    }
    public Result code(Integer code){
        this.setCode(code);
        return this;
    }
    public Result data(String key, Object value){
        this.data.put(key, value);
        return this;
    }
    public Result data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}
SuccessHandler
@Component
public class SuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {  // 登录成功自定义处理

    // 对象与 json 转换类
    private static ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        response.setContentType("application/json;charset=UTF-8");  // 定义返回 json
        response.getWriter().write(objectMapper.writeValueAsString(Result.success()));
    }
}
FailHandler
@Component
public class FailHandler extends SimpleUrlAuthenticationFailureHandler {

    private static ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(Result.error().message("用户名或密码错误")));
    }
}
配置方法
http.formLogin()
    .successHandler(successHandler)  // 和 .defaultSuccessUrl() 二选一
    .failureHandler(failHandler);    // 同理

登出

退出的默认行为

  • 使当前 session 失效:回收权限
  • 删除 RememberMe 信息(包括数据库的 Token)
  • 清除当前的 SecurityContext 信息
  • 重定向到登录页面(loginPage 配置指定页面)

配置文件

http.logout()                                    // 以下可选
        .logoutUrl("/signOut")                   // 跳转 URL(与logoutSuccessHandler 二选一)
        .logoutSuccessUrl("/logoutSuccess")      // 自定义退出页面(需要配置所有人可访问的页面)
        .deleteCookies("JSESSIONID")             // 删除 cookie 中的 session id(也可删除其他)
    	// .logoutSuccessHandler(logoutHandler)  // 自定义退出逻辑
    	// 需要自定义一个 Handler 实现 logoutSuccessHandler 接口

Session 管理

常用配置

server:
  servlet:
    session:
      timeout: 300s    # 默认 30min, 如果低于 1min, 会设置成 1min
      cookie:
        http-only: true  # 脚本无法访问 cookie
        secure: true     # 仅允许 https 协议发送 cookie

创建策略

  • always :如果当前请求没有 session ,则创建
  • never :有 session 则使用,没有也不创建
  • ifRequired:在需要时才创建 session (默认)
  • stateless:不会创建和使用任何 session ,适用于无状态应用

Session 保护机制

  • migrateSession :每次登录创建新的 session,复制属性并使旧 session 失效(默认)
  • changeSessionId:每次登录只更换 sessionID
  • newSession:每次登录创建一个新 session
  • none:直接使用原来的 session

超时配置类

// 编写超时回调方法
public class SessionExpiredStrategy implements SessionInformationExpiredStrategy {

    // 对象与 json 转换类
    private static ObjectMapper objectMapper = new ObjectMapper();


    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        // 超时时候回调方法
        event.getResponse().setContentType("application/json;charset=UTF-8");
        event.getResponse().getWriter().write(
                objectMapper.writeValueAsString(
                    Result.error().code(
                        Result.ResultCode.TIMEOUT).message("你的账号在别处登录,被迫下线")));
    }
}

配置类

http.sessionManagement()
	.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)  // session 创建策略
    .invalidSessionUrl("/login")                               // 超时跳转 URL
    .sessionFixation().none()                                  // 配置 session 保护
    .maximumSessions(1)                                        // 最大 session 连接数
    // true:不可再次登录;false:之前登录的 session 会下线
    .maxSessionsPreventsLogin(false)
    .expiredSessionStrategy(new SessionExpiredStrategy())      // 超时配置
    

RBAC 权限管理模型

  • 用户:系统接口及访问的操作者
  • 权限:能够访问接口或操作的的授权资格
  • 角色:具有一定数量操作权限的集合

// TODO

权限配置

动态加载权限信息

  • 配置用户信息
  • 编写 Dao层
  • 实现 loadUserByUsername 方法

这里的代码都是在使用 Mybatis Plus 的代码生成器上添加部分代码

编写用户信息
// User.java
// 主要需要实现 UserDetails 接口,本身 User 会存放用户信息
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("ams_user")
@ApiModel(value="User对象", description="")
public class User implements UserDetails,Serializable {

    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "用户名 ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    private Integer orgId;

    private Integer enabled;

    private Collection<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

编写 UserMapper
@Repository
public interface UserMapper extends BaseMapper<User> {

    /**
     * 根据用户名返回用户信息
     * @param username 用户名
     * @return UserDetailsImpl
     */
    @Select("SELECT username, password, enabled\n" +
            "FROM ams_user\n" +
            "WHERE username = #{username}")
    User findUserByUsername(@Param("username") String username);

    /**
     * 根据用户名返回角色列表
     * @param username 用户名
     * @return List<String>
     */
    @Select("SELECT code\n" +
            "FROM ams_role r\n" +
            "LEFT JOIN ams_user_role ur ON r.id = ur.role_id\n" +
            "LEFT JOIN ams_user u ON u.id = ur.user_id\n" +
            "WHERE u.username = #{username}")
    List<String> findRoleByUsername(@Param("username") String username);

    /**
     * 根据角色列表查询用户权限
     * @param roleCodes 角色列表
     * @return List<String>
     */
    @Select({
        "<script>",
            "SELECT url ",
            "FROM ams_menu m ",
            "LEFT JOIN ams_role_menu rm ON m.id = rm.menu_id ",
            "LEFT JOIN ams_role r ON r.id = rm.role_id ",
            "WHERE r.code IN ",
            "<foreach collection = 'roleCodes' item = 'roleCode' open = '(' separator = ',' close = ')' >",
                "#{roleCode}",
            "</foreach>",
        "</script>"
    })
    List<String> findAuthorityByRoleCodes(@Param("roleCodes")List<String> roleCodes);
}
实现 loadUserByUsername 方法
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 
    implements UserService, UserDetailsService {

    UserMapper mapper;

    @Autowired
    public UserServiceImpl(UserMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public UserDetails loadUserByUsername(String username) 
        throws UsernameNotFoundException {
        // 加载基础用户信息
        User user = mapper.findUserByUsername(username);
        // 加载用户角色表
        List<String> roleCodes = mapper.findRoleByUsername(username);
         // 通过用户角色表加载权限
        List<String> authoritys = mapper.findAuthorityByRoleCodes(roleCodes);
        // 角色也是特殊的权限,加上 ROLE_ 前缀后加入权限列表
        roleCodes = roleCodes.stream().map(rc -> "ROLE_" + rc).collect(Collectors.toList());
        authoritys.addAll(roleCodes);
        // 权限列表加入到 UserDetails
        user.setAuthorities(
                // commaSeparatedStringToAuthorityList 接受逗号分隔的字符串
                AuthorityUtils.commaSeparatedStringToAuthorityList(
                        String.join(",", authoritys)));
        return user;
    }

}

动态鉴权

编写 Dao 层
@Repository
public interface AuthorizeMapper {

    /**
     * 查询权限 URL
     * @param username 用户名
     * @return Lise<String>
     */
    @Select("SELECT url\n" +
            "FROM ams_menu m\n" +
            "LEFT JOIN ams_role_menu rm ON m.id = rm.menu_id\n" +
            "LEFT JOIN ams_role r ON r.id = rm.role_id\n" +
            "LEFT JOIN ams_user_role ur ON r.id = ur.role_id\n" +
            "LEFT JOIN ams_user u ON u.id = ur.user_id\n" +
            "WHERE u.username = #{username}")
    List<String> findUrlByUsername(@Param("username") String username);
}
编写 Service 层
public interface AuthorizeService {

    /**
     * 判断用户是否具有 request 请求中的权限
     * @param request Request 请求
     * @param authentication 权限认证接口
     * @return boolean
     */
    boolean hasPermission(HttpServletRequest request, Authentication authentication);


}
@Service
public class AuthorizeServiceImpl implements AuthorizeService {

    private AuthorizeMapper mapper;

    // URL 匹配工具类
    private AntPathMatcher matcher = new AntPathMatcher();

    @Autowired
    public AuthorizeServiceImpl(AuthorizeMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public boolean hasPermission(HttpServletRequest request, 
                                 Authentication authentication) {
        // 验证用户的 UserDetails,为了获取用户名
        Object principal = authentication.getPrincipal();
        if(principal instanceof UserDetails) {
            String username = ((UserDetails) principal).getUsername();
            // 根据用户名查询其权限列表
            List<String> urls = mapper.findUrlByUsername(username);
            // 遍历 urls 是否存在权限
            return urls.stream().anyMatch(
                url -> matcher.match(url, request.getRequestURI()));
        }
        return false;
    }
}

配置使用

// SecurityConfig.java 
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .formLogin()
        .successHandler(successHandler)
        .failureHandler(failHandler)
        .and().authorizeRequests()
        .antMatchers("/login").permitAll()
        .antMatchers("/index").authenticated()
        // 其他页面需要通过该类认证才可以访问
        .anyRequest().access("@authorizeServiceImpl.hasPermission(request, authentication)");

}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService)
        .passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

权限表达式

常用权限表达式
表达式函数 描述
hasRole([role]) 拥有指定的角色返回 true
tips: Spring Security 会给角色带上 ROLE_
hasAnyRole([role1, role2]) 拥有任意一个角色返回 true
hasAuthority([authority]) 拥有指定权限则返回 true
hasAnyAuthority([auth1, auth2]) 拥有任意一个权限返回 true
permitAll 永远返回 true
denyAll 永远返回 false
anonymous 当用户是匿名用户时返回 true
rememberMe 当前用户是 rememberMe 用户时返回 true
authentication 当前用户是否登录认证成功
fullAuthenticated 当前用户既不是 anonymous 也不是 rememberMe 时返回 true
hasIpAress(‘192.168.1.1/24') 请求发送的 ip 匹配时返回 true
权限表达式在全局配置中的使用
.access("xxx")
方法级别的安全控制
Jsr - 250 注解

需要在 Spring Security 的配置类上开启 @EnableGlobalMethodSecurity(jsr250Enabled=true)

注解 描述
@DenyAll 拒绝所有访问
@RolesAllowed({“user", “admin"}) 拥有 useradmin 任意一个角色即可访问
tips:这里可以省略 ROLE_
@PermitAll 允许所有访问
secured 注解

需要在 Spring Security 的配置类上开启 @EnableGlobalMethodSecurity(securedEnabled=true)

注解 描述
@Secured("ROLE_user","ROLE_admin") 拥有 useradmin 任意一个角色即可访问

IS_AUTHENTICATED_ANONYMOUSLY 表示允许匿名用户访问

prePost 注解 与 EL 表达式

需要在 Spring Security 的配置类上开启 @EnableGlobalMethodSecurity(prePostEnabled=true)

注解 描述
@PreAuthorize 在方法执行执行,基于表达式结果来限制方法
@PostAuthorize 在方法执行执行,如果表达式结果为 false,会抛出异常
@PreFilter 在方法执行执行,过滤参数
@PostFilter 在方法执行执行,过滤结果
表达式函数 描述
hasRole([role]) 拥有指定的角色返回 true
tips: Spring Security 会给角色带上 ROLE_
hasAnyRole([role1, role2]) 拥有任意一个角色返回 true
hasAuthority([authority]) 拥有指定权限则返回 true
hasAnyAuthority([auth1, auth2]) 拥有任意一个权限返回 true
Principle 代表当前用户的 principle 对象
authentication 直接从 SecurityContext 获取的当前 Authentication 对象
permitAll 永远返回 true
denyAll 永远返回 false
isAnonymous() 当前用户是否是匿名用户
isRememberMe() 当前用户是否是 rememberMe 用户
isAuthenticated() 当前用户是否登录认证成功
isFullyAuthenticated() 当前用户既不是 anonymous 也不是 rememberMe 时返回 true
// 示例
@PreAuthorize("hasRole('admin')")  // 若没有 admin 角色,则抛出异常
public String findALl(){ return null}

@PostAuthorize("returnObject.username == #username") // username 为返回的 username 则不报错
public User find(@Param("username")String username) {
    User user = new User();
    user.setUsername(username);
    return user;
}

@PreFilter(filterTarget = "ids", value = "filterObject%2 == 0")  // 过滤 list 中的奇数
public String del(List<Integer> ids) {
    return null;
}

// 过滤掉 list 中不在 authentication中的对象
@PostFilter("returnObject.name == authentication.name")
public List<String> findAllUser() {
    List<User> list = new ArrayList<>();
    list.add(new User("xxx"));
    list.add(new User("yyy"));
    return list;
}

RememberMe功能

配置文件

前端需要参数名需要与 rememberMeParameter 一致,默认:remember-me

http.rememberMe()                      // 开启 rememberMe 功能(以下可选)
    .rememberMeParameter("remember")   // 指定传递的参数名(需与前端一致)
    .rememberMeCookieName("remember")  // 指定返回 cookie 中参数的名字
    .tokenValiditySeconds(2*24*60*60)  // 指定 rememberMe 过期时间
    .tokenRepository(persistentTokenRepository())  // 数据持久化配置

数据持久化

创建表
CREATE TABLE `login_token` (
	`series` varchar(64) NOT NULL,
	`username` varchar(32) NOT NULL,
	`token` varchar(64) NOT NULL,
	`last_used` datetime NOT NULL COMMENT '需要自行设置',
	PRIMARY KEY(`series`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
配置 persistentTokenRepository
// SecurityConfig.java
@Bean
public PersistentTokenRepository persistentTokenRepository() {

    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource);
    return tokenRepository;
}

验证码

验证方式

  • session 存储验证码
  • 基于对称算法的验证码

Session 存储验证码

配置图片验证码工具类
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
配置类
// 更多配置详见下表
@Component
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha getKaptcha() {
        
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        
        properties.setProperty("kaptcha.image.width", "150");
        
        kaptcha.setConfig(new Config(properties));
        return kaptcha;
    }
}
配置 描述 默认值
kaptcha.border 图片边框,合法值:yes , no yes
kaptcha.border.color 边框颜色,合法值: r,g,b 或者 white,black,blue. black
kaptcha.border.thickness 边框厚度,合法值:>0 1
kaptcha.image.width 图片宽 200
kaptcha.image.height 图片高 50
kaptcha.producer.impl 图片实现类 DefaultKaptcha
kaptcha.textproducer.impl 文本实现类 DefaultTextCreator
kaptcha.textproducer.char.string 文本集合,验证码值从此集合中获取 abcde2345678gfynmnpwx
kaptcha.textproducer.char.length 验证码长度 5
kaptcha.textproducer.font.names 字体 Arial, Courier
kaptcha.textproducer.font.size 字体大小 40px
kaptcha.textproducer.font.color 字体颜色,合法值: r,g,b 或者 white,black,blue. black
kaptcha.textproducer.char.space 文字间隔 2
kaptcha.noise.impl 干扰实现类 DefaultNoise
kaptcha.noise.color 干扰颜色,合法值: r,g,b 或者 white,black,blue. black
kaptcha.obscurificator.impl 图片样式:
水纹 WaterRipple
鱼眼 FishEyeGimpy
阴影 ShadowGimpy
WaterRipple
kaptcha.background.impl 背景实现类 DefaultBackground
kaptcha.background.clear.from 背景颜色渐变,开始颜色 light grey
kaptcha.background.clear.to 背景颜色渐变, 结束颜色 white
kaptcha.word.impl 文字渲染器 DefaultWordRenderer
kaptcha.session.key session key KAPTCHA_SESSION_KEY
kaptcha.session.date session date KAPTCHA_SESSION_DATE
posted @ 2020-07-30 08:36  Posase  阅读(351)  评论(0编辑  收藏  举报