Springboot-主线7-Shiro学习

Shiro

引言:

权限管理

权限管理包括用户身份认证授权两部分,简称认证授权,对于需要访问控制的资源用户首先经过身份认证,认证同构后用户具有对该资源的访问权限方可访问。

身份认证

就是判断一个用户是否为合法用户的处理过程,

什么是授权

授权即访问控制,控制什么人具有访问什么资源的权力。

shiro

结构图

image-20201205172206690

shiro的认证

关键对象

  • Subject:主体

​ 访问系统的用户,主题可以是用户、程序等,进行认证的都被称为主体。

  • Principal:身份信息

是主体进行身份认证的表示,标识必须具有唯一性,一个主体可以具有多个身份。

  • credential:凭证信息

是只有主体自己知道的安全信息,如密码,证书等。

认证流程

image-20201205174143847

操作实例

添加依赖:

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.5.3</version>
        </dependency>

我们可以查看securityManager的结构图:

image-20201205175209090

我们可以选择一个单机java测试一下:

前提需要创建一个shiro.ini文件:

[users]
wangyun=123
zhangsan=123
lisi=789
public static void main(String[] args) {
        // 创建安全管理七对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        // 给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));

        // SecurityUtils 全局安全工具类
        SecurityUtils.setSecurityManager(securityManager);

        // 关键对象 主体
        Subject subject = SecurityUtils.getSubject();
        //  创建token
        UsernamePasswordToken token = new UsernamePasswordToken("wangyun","123");
        try{
            subject.login(token); // 用户认证
            System.out.println("认证状态"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("认证失败:用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("认证失败:密码错误");
        }

    }
认证的源码分析!!!

我们通过debug进入login方法中,

image-20201205212746718

发现起作用的还是securityManage的login的方法,

最终验证用户名是simpleAccountRealm中的doGetAuthenticationInfo方法中。

密码校验是在AuthenticatingRealm中的assertCredentialsMatch。

所以,追溯源码,可以发现

AuthenticatingRealm ->认证realm doGetAuthenticationInfo

AuthorizingRealm — 授权realm doGetAuthorizationInfo

自定义realm
public class SelfRealm extends AuthorizingRealm {

    @Override
    // 授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    // 认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        Object principal = authenticationToken.getPrincipal();
        System.out.println(principal);
        if("wangyun".equals(principal)){
            /**
             * 参数1:正确的数据库的用户名
             * 参数2 :正确密码
             * 参数3:提供当前realm的名字
             */
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,"123",this.getName());
            return info;
        }
        return null;
    }
}
加盐

我们可以发现,没有做到绝对的安全,

实际应用过程中是将盐和散列后的值存在数据库中,自动realm从数据库取出盐和加密后 的值由shiro完成密码校验。

测试md5加密

image-20201205222903368

realm设置hash凭证匹配器

// 设置realm使用hash凭证匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // 设置md5算法
        matcher.setHashAlgorithmName("md5");
        // 散列次数
        //matcher.setHashIterations(1024);
        realm.setCredentialsMatcher(matcher);
        manager.setRealm(realm);

自定义realm中的代码:

// md5加密
            // 参数三:加随机盐,日后应从数据库中获取
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo
                    (principal,
                            "b39a5b268ace2b76d989c09a2766a4cd",
                            ByteSource.Util.bytes("qt*"),
                            this.getName());
            return info;

shiro的授权

关键对象

Who ,即主体(Subject), 主体需要访问系统中的资源。

What,即资源(Resource),包括资源类型和资源实例。

How,权限/许可(Permission),规定了主体对资源的操作许可。

授权过程

image-20201206130521989

授权方式
  • 基于角色的访问控制,是以角色为中心进行访问控制。
if(Subject.hasRole("admin")){
	// TODO
}
  • 基于资源的访问控制,是以资源为中心进行访问控制。
if(Subject.isPermission("user:create")){
	// TODO
}
权限字符串

​ 权限字符串的规则是:资源操作符:``操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作。

例如:

// 认证用户进行授权
        if (subject.isAuthenticated()) {
            // 1. 基于角色权限控制
            System.out.println(subject.hasRole("admin"));

            // 2. 基于多角色权限控制
            System.out.println(subject.hasAllRoles
                    (Arrays.asList("admin", "user")));
            // 3. 是否具有其中一个角色
            boolean[] roles = subject.hasRoles(Arrays.asList("user", "admin"));
            for (boolean role : roles) {
                System.out.println(role);
            }
            // 4, 基于权限字符创的访问控制 资源操作符:操作:资源实例标识符
            System.out.println("权限--->" + subject.isPermitted("admin:create:*"));
        }
 @Override
    // 授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("-----进入授权-------");
        // 获取主身份信息
        String principal = (String) principalCollection.getPrimaryPrincipal();
        log.info("身份信息---->{}",principal);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole("admin");
        info.addStringPermission("admin:*:*");
        return info;
    }

shiro与Springboot

流程图

image-20201206151551224

实例:

首先我们要使用Shiro,我们肯定需要先导入依赖:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-annotation</artifactId>
            <version>3.3.2</version>
        </dependency>
    </dependencies>

接下来,创建ShiroConfig的配置项以及自定义的realm。

/** ShrioConfig **/
@Configuration
public class ShiroConfig{
    //1. 创建shiroFilter
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 1. 给filter设置安全管理器
        factoryBean.setSecurityManager(defaultWebSecurityManager);
        // 4. 配置系统的受限资源
        Map<String, String> map = new HashMap<>();
        map.put("/user/login","anon");
        map.put("/to/register","anon");
        map.put("/register","anon");
        map.put("/**","authc");  //authc:请求这个资源需要认证和授权
        // 5. 默认认证界面
        factoryBean.setLoginUrl("/login.html");
        factoryBean.setFilterChainDefinitionMap(map);
        //
        return factoryBean;
    }

    // 2. 创建安全管理器
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        defaultSecurityManager.setRealm(getRealm());
        return defaultSecurityManager;
    }

    // 3. 创建自定义realm
    @Bean
    public Realm getRealm(){
        SPRealm realm = new SPRealm();
        // 修改凭证校验匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");

        // 设置散列数
        matcher.setHashIterations(1024);
        realm.setCredentialsMatcher(matcher);
        return realm;
    }

}
/** Realm 认证**/
@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("----------");
        // 根据身份信息
        String principal = (String) authenticationToken.getPrincipal();
        User user = userService.findByUserName(principal);
        System.out.println(user.toString());
        if (principal.equals("zhangsan")) {
            return new SimpleAuthenticationInfo(principal, user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()),this.getName());
        }
        return null;
    }

依次创建Controller,Service,mapper,这里只展示Controller

public class LoginController {
    @Autowired
    private UserService userService;
    static Map<String,String> userMap=new HashMap<>();
    static{
        userMap.put("zhangsan","123");
        userMap.put("zhangsan","321");
        userMap.put("wangyun","123");
    }


    @RequestMapping(value = {"/","/index"},method = RequestMethod.GET)
    public String index(){
        return "login";
    }


    @PostMapping("/user/login")
    public String tologin(@RequestParam(value = "username",required = false) String username,
                          @RequestParam(value="password",required = false) String password){
        log.info("登录执行->{},{}",username,password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username,password));
            return "main";
        }catch (UnknownAccountException e){
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            System.out.println("密码错误");
        }
        return "login";
    }
    @RequestMapping(value = "/loginout",method = RequestMethod.GET)
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }
    @RequestMapping("register")
    public String register(User user){
        try{
            System.out.println(user.getUsername()+user.getPassword());
            userService.register(user);
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("注册失败");
            return "register";
        }
        System.out.println("注册成功");
        return "login";
    }
    @RequestMapping("to/register")
    public String toRegister(User user){
        return "register";
    }


}

此时为了完成MD5+加盐的操作,我们还需要一个加盐工具类。

public class SaltUtils {

    public static String getSalt(int n){
        char[] chars = "!#$%^&*$#$%^&*^%$##@$%^&*%#$%^@#$".toCharArray();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            char aChar=chars[new Random().nextInt(chars.length)];
            sb.append(aChar);
        }

        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println(SaltUtils.getSalt(8));
    }

}

到这里为止,已初步完成shiro的认证环节。

授权操作

    @RequestMapping("save")
    @RequiresRoles("admin") // 用来判断角色
    @RequiresPermissions("admin:update:*")
    public String save(){
        Subject subject = SecurityUtils.getSubject();
        System.out.println("保存");
        return "main";
    }

授权

shiro整合redis

首先我们需要先启动redis的服务器,在application配置文件中,加上redis的相关配置

导入依赖

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改实体类,继承Serializable接口,实现序列化

并且在shrio中,由于SimpleAuthenticationInfo没有继承父类的无参构造,导致在对盐进行转化的时候不能实现反序列化。我们只能自己手写一个类,

public class MyByteSource implements ByteSource, Serializable {

    /*这里将final去掉了,去掉后要在后面用getter和setter赋、取值*/
    private byte[] bytes;
    private String cachedHex;
    private String cachedBase64;

    /*添加了一个无参构造方法*/
    public MyByteSource(){}

    public MyByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public MyByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public MyByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public MyByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public MyByteSource(File file) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
    }

    public MyByteSource(InputStream stream) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }


    /*这里加了getter和setter*/
    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public byte[] getBytes() {
        return this.bytes;
    }

    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    public String toBase64() {
        if (this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    public String toString() {
        return this.toBase64();
    }

    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof ByteSource) {
            ByteSource bs = (ByteSource)o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }

    /*取代原先加盐的工具类*/
    public static class Util{
        public static ByteSource bytes(byte[] bytes){
            return new MyByteSource(bytes);
        }

        public static ByteSource bytes(String arg0){
            return new MyByteSource(arg0);
        }
    }
}

自定义RedisCacheManager

public class RedisCacheManager implements CacheManager {
    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        System.out.println("RedisCacheManager.getCache被调用:" + s);
        return new RedisCache<K,V>(s);
    }
}

自定义RedisCache,实现Cache接口

@Component
public class RedisCache<k,v> implements Cache<k,v> {
    private String cacheName;

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }
    public RedisCache() {
    }

    @Override
    public v get(k k) throws CacheException {
        RedisTemplate redisTemplate = getRedistemplate();
        return (v) redisTemplate.opsForHash().get(this.cacheName, k.toString());
    }

    @Override
    public v put(k k, v v) throws CacheException {
        System.out.println("put key:" + k);
        System.out.println("put value:" + v);
        RedisTemplate redisTemplate = getRedistemplate();
        redisTemplate.opsForHash().put(this.cacheName, k.toString(), v);
        return null;
    }

    @Override
    public v remove(k k) throws CacheException {
        return null;
    }

    @Override
    public void clear() throws CacheException {

    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public Set<k> keys() {
        return null;
    }

    @Override
    public Collection<v> values() {
        return null;
    }
    
    private RedisTemplate getRedistemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

不过这里需要注意的事,这里RedisTemplate方法,不能自动注入,因为该类此时还没有交给Spring容器所管理,需要我们自己创建一个工具类

@Component
public class ApplicationContextUtils implements ApplicationContextAware {
    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("setApplicationContext 方法执行了");
        this.context = applicationContext;
        System.out.println(context);
    }
    //通过ApplicationContext获取对应的bean
    public static Object getBean(String beanName){
        return context.getBean(beanName);
    }
}

开启缓存管理器

@Bean
    public Realm getRealm(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        hashedCredentialsMatcher.setHashIterations(1024);
        CustomerRealm customerRealm = new CustomerRealm();
        customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        //设置缓存管理器
//        customerRealm.setCacheManager(new EhCacheManager());
        customerRealm.setCacheManager(new RedisCacheManager());
        //开启缓存管理
        customerRealm.setCachingEnabled(true);
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        customerRealm.setAuthorizationCacheName("authorizationCache");
        return customerRealm;
    }

图片验证码功能

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;

import javax.imageio.ImageIO;

public class VerifyCodeUtils{

    //使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
    public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
    private static Random random = new Random();

    /**
     * 使用系统默认字符源生成验证码
     * @param verifySize    验证码长度
     * @return
     */
    public static String generateVerifyCode(int verifySize){
        return generateVerifyCode(verifySize, VERIFY_CODES);
    }
    /**
     * 使用指定源生成验证码
     * @param verifySize    验证码长度
     * @param sources   验证码字符源
     * @return
     */
    public static String generateVerifyCode(int verifySize, String sources){
        if(sources == null || sources.length() == 0){
            sources = VERIFY_CODES;
        }
        int codesLen = sources.length();
        Random rand = new Random(System.currentTimeMillis());
        StringBuilder verifyCode = new StringBuilder(verifySize);
        for(int i = 0; i < verifySize; i++){
            verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));
        }
        return verifyCode.toString();
    }

    /**
     * 生成随机验证码文件,并返回验证码值
     * @param w
     * @param h
     * @param outputFile
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, outputFile, verifyCode);
        return verifyCode;
    }

    /**
     * 输出随机验证码图片流,并返回验证码值
     * @param w
     * @param h
     * @param os
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, os, verifyCode);
        return verifyCode;
    }

    /**
     * 生成指定验证码图像文件
     * @param w
     * @param h
     * @param outputFile
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
        if(outputFile == null){
            return;
        }
        File dir = outputFile.getParentFile();
        if(!dir.exists()){
            dir.mkdirs();
        }
        try{
            outputFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(outputFile);
            outputImage(w, h, fos, code);
            fos.close();
        } catch(IOException e){
            throw e;
        }
    }

    /**
     * 输出指定验证码图片流
     * @param w
     * @param h
     * @param os
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
        int verifySize = code.length();
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Random rand = new Random();
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        Color[] colors = new Color[5];
        Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
                Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                Color.PINK, Color.YELLOW };
        float[] fractions = new float[colors.length];
        for(int i = 0; i < colors.length; i++){
            colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
            fractions[i] = rand.nextFloat();
        }
        Arrays.sort(fractions);

        g2.setColor(Color.GRAY);// 设置边框色
        g2.fillRect(0, 0, w, h);

        Color c = getRandColor(200, 250);
        g2.setColor(c);// 设置背景色
        g2.fillRect(0, 2, w, h-4);

        //绘制干扰线
        Random random = new Random();
        g2.setColor(getRandColor(160, 200));// 设置线条的颜色
        for (int i = 0; i < 20; i++) {
            int x = random.nextInt(w - 1);
            int y = random.nextInt(h - 1);
            int xl = random.nextInt(6) + 1;
            int yl = random.nextInt(12) + 1;
            g2.drawLine(x, y, x + xl + 40, y + yl + 20);
        }

        // 添加噪点
        float yawpRate = 0.05f;// 噪声率
        int area = (int) (yawpRate * w * h);
        for (int i = 0; i < area; i++) {
            int x = random.nextInt(w);
            int y = random.nextInt(h);
            int rgb = getRandomIntColor();
            image.setRGB(x, y, rgb);
        }

        shear(g2, w, h, c);// 使图片扭曲

        g2.setColor(getRandColor(100, 160));
        int fontSize = h-4;
        Font font = new Font("Algerian", Font.ITALIC, fontSize);
        g2.setFont(font);
        char[] chars = code.toCharArray();
        for(int i = 0; i < verifySize; i++){
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
            g2.setTransform(affine);
            g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
        }

        g2.dispose();
        ImageIO.write(image, "jpg", os);
    }

    private static Color getRandColor(int fc, int bc) {
        if (fc > 255)
            fc = 255;
        if (bc > 255)
            bc = 255;
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    private static int getRandomIntColor() {
        int[] rgb = getRandomRgb();
        int color = 0;
        for (int c : rgb) {
            color = color << 8;
            color = color | c;
        }
        return color;
    }

    private static int[] getRandomRgb() {
        int[] rgb = new int[3];
        for (int i = 0; i < 3; i++) {
            rgb[i] = random.nextInt(255);
        }
        return rgb;
    }

    private static void shear(Graphics g, int w1, int h1, Color color) {
        shearX(g, w1, h1, color);
        shearY(g, w1, h1, color);
    }

    private static void shearX(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);

        for (int i = 0; i < h1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(0, i, w1, 1, (int) d, 0);
            if (borderGap) {
                g.setColor(color);
                g.drawLine((int) d, i, 0, i);
                g.drawLine((int) d + w1, i, w1, i);
            }
        }
    }

    private static void shearY(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(40) + 10; // 50;

        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < w1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(i, 0, 1, h1, 0, (int) d);
            if (borderGap) {
                g.setColor(color);
                g.drawLine(i, (int) d, i, 0);
                g.drawLine(i, (int) d + h1, i, h1);
            }
        }
    }

    public static void main(String[] args) throws IOException{
        File dir = new File("D:/upload/verifyCode");
        int w = 200, h = 80;
        for(int i = 0; i < 50; i++){
            String verifyCode = generateVerifyCode(4);
            File file = new File(dir, verifyCode + ".jpg");
            outputImage(w, h, file, verifyCode);
        }
    }
}

控制器的验证码规则

 @RequestMapping("getImage")
    public void getImage(HttpSession session, HttpServletResponse response) throws IOException {
        //生成验证码
        String code = VerifyCodeUtils.generateVerifyCode(4);
        //验证码存入session
        session.setAttribute("code",code);
        //验证码生成图片
        ServletOutputStream os = response.getOutputStream();
        response.setContentType("image/png");
        VerifyCodeUtils.outputImage(220,60,os, code);
    }

声明

借鉴B站不良人大大,欢迎各位博友观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值