配置文件:

application.properties:

# 应用服务 WEB 访问端口
server.port=8081
server.servlet.context-path=/shiro
# 应用名称
spring.application.name=shirospring.mvc.view.prefix=/
spring.mvc.view.suffix=.jspspring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456mybatis.type-aliases-package=com.li.springboot_jsp_shiro.entity
mybatis.mapper-locations=classpath:com/li/mapper/*.xml

pom.xml: 

        <!--引入jsp依赖解析--><!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core --><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId></dependency><!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl --><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version>1.5.3</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.8</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.19</version></dependency>

1.验证功能:

(1)代码部分:

  shiro.int:

[users]
li=123
yang=456
su=789

ps:shiro的配置文件是提供权限数据的,以.ini结尾。

  Authenticator.java:

 //1、创建安全管理器对象DefaultSecurityManager securityManager = new DefaultSecurityManager();//2.给安全管理器设置realmsecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));//3.SecurityUtils给全局安全工具类设置安全管理器SecurityUtils.setSecurityManager(securityManager);//4.获取主题对象Subject subject = SecurityUtils.getSubject();//5.创建令牌UsernamePasswordToken token = new UsernamePasswordToken("li", "123");try {subject.login(token);//用户验证System.out.println("认证状态:"+subject.isAuthenticated());}catch (Exception e){e.printStackTrace();}

实现自定义realm:

重写AuthorizingRealm中的doGetAuthorizationInfo方法,将认证或授权的数据来源装换成数据库的实现:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//在token中获取用户名
String principal = (String) token.getPrincipal();
System.out.println(principal);
if("li".equals(principal)){SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "123", this.getName());return simpleAuthenticationInfo;}return null;}

(2)认证过程的源码总结:

1.执行用户名比较:在SimpleAccountRealm中的doGetAuthenticationInfo方法中比较。

2.密码校验:在AuthenticatingRealm中的assertCredentialsMath方法中比较。

注意区分:AuthenticatingRealm通过doGetAuthenticationInfo方法认证realm;

AuthorrzingRealm通过doGetAuthorizationInfo方法授权realm。

认证过程中真正用到的类的关系图

(3)MD5算法

常用来加密或者签名(校验和),其生成结果为16进制32位的字符串。

特点:不可逆,内容相同生成的结果都一样,无论执行多少次。比较两个.txt文件可用MD5算法。

ps: 网上的在线MD5加密解密网站都是使用穷举法来解密的。

MD5要和Salt(随机盐)搭配使用,在数据库中要保存Salt

1.MD5具体使用:

Md5Hash md5Hash = new Md5Hash("123");
System.out.println(md5Hash.toHex());//结果为202cb962ac59075b964b07152d234b70

2.MD5+Salt:

Md5Hash md5Hash2 = new Md5Hash("123","X0*7ps");
System.out.println(md5Hash2.toHex());//结果为8a83592a02263bfe6752b2b5b03a4799

3.MD5+Salt+hash散列:

Md5Hash md5Hash3 = new Md5Hash("123","X0*7ps",1024);
System.out.println(md5Hash3.toHex());//e4f9bf3e0c58f045e62c23c533fcf633

 4.代码(不全,只拿了和之前不一样的核心部分代码):

//1.创建安全管理器对象
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//2.注入realm
CustomerMD5Realm realm = new CustomerMD5Realm();
//使用hash凭证适配器设置realm
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");//设置算法
//credentialsMatcher.setHashIterations(1024);//设置散列值
realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(realm);

再重写授权的方法:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//在token中获取用户名
String principal = (String) token.getPrincipal();
if("li".equals(principal)){
return new SimpleAuthenticationInfo(principal,
"8a83592a02263bfe6752b2b5b03a4799", ByteSource.Util.bytes("X0*7ps"),this.getName());}return null;
}

2.授权功能:

代码(不全,只拿了和之前不一样的核心部分代码):

 //授权if(subject.isAuthenticated()){//基于角色权限控制System.out.println(subject.hasRole("admin"));//基于多角色权限控制,两个角色都有才返回trueSystem.out.println(subject.hasAllRoles(Arrays.asList("admin","user")));//是否具有其中一个角色boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));for (boolean aBoolean : booleans) {System.out.println(aBoolean);}//基于权限字符串的访问控制System.out.println("权限:"+subject.isPermitted("user:*:01"));//同时具有哪些属性boolean permittedAll = subject.isPermittedAll("suer:*:01", "product:*");System.out.println(permittedAll);}
 @Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String primaryPrincipal = (String)principals.getPrimaryPrincipal();System.out.println("权限信息:"+primaryPrincipal);//根据用户信息来获取当前的角色信息和权限信息SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();//从数据库中查询角色信息赋值给权限对象simpleAuthorizationInfo.addRole("admin");simpleAuthorizationInfo.addRole("user");//从数据库中查询权限信息赋值给权限对象simpleAuthorizationInfo.addStringPermission("user:*:001");return simpleAuthorizationInfo;}

3.Shiro结合SpringBoot:

3.1 使用jsp整合:

ps: springboot整合jsp时记得设置如下图所示的工作空间

index.jsp:

<h1>系统主页</h1>
<a type="${pageContext.request.contextPath}/user/logout">退出用户</a>
<ul><li><a href="">用户管理</a></li><li><a href="">商品管理</a></li><li><a href="">订单管理</a></li><li><a href="">物流管理</a></li>
</ul>

ps:可以在主页面使用一些别的标签。

<shiro:principal></shiro:principal>   实现登陆用户的姓名

login.jsp:

<h1>用户登陆</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">用户名:<input type="text" name="username"><br/>密码:<input type="text" name="password"><br/><input type="submit" value="登陆">
</form>

首先需要编写一个类来整合Shiro框架,如下:

@Configuration
public class ShiroConfig {@Bean//创建ShiroFilter,拦截所有请求public ShiroFilterFactoryBean getShiroFilterFactory(DefaultWebSecurityManager defaultWebSecurityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//给Filter设置安全管理器shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);//配置系统受限资源//配置系统公共资源Map<String,String> map = new HashMap<>();map.put("/user/login","anon");//将login页面设为公共资源,否则当执行到下一句时会进入死循环,一直不断地跳转到登陆页面。这里需要注意路径有没有写错!map.put("/user/register","anon");//后面会用到map.put("/index","authc");//authc代表资源需要认证和授权,具体可以查一下shiro的常见过滤器。注意,若arg1为/**,则表示除了login.jsp之外的所有页面都需要验证和授权//设置默认的验证页面路径shiroFilterFactoryBean.setLoginUrl("/login.jsp");return shiroFilterFactoryBean;}//创建安全管理器public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();//设置安全管理器defaultWebSecurityManager.setRealm(realm);return defaultWebSecurityManager;}//创建自定义realmpublic Realm getRealm(){CustomerRealm customerRealm = new CustomerRealm();//修改凭证校验匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();//设置加密算法为MD5credentialsMatcher.setHashAlgorithmName("MD5");//设置散列的次数credentialsMatcher.setHashIterations(1024);return customerRealm;}

(1)验证和退出功能的实现:

@Controller
@RequestMapping("user")
public class UserController {/*** 退出* @return*/@RequestMapping("logout")public String logout(){Subject subject = SecurityUtils.getSubject();subject.logout();return "redirect:/login.jsp";}/*** 处理身份验证* @param username* @param password* @return*/@RequestMapping("login")public String login(String username,String password){//获取主体对象Subject subject = SecurityUtils.getSubject();try {subject.login( new UsernamePasswordToken(username,password));return "redirect:/index.jsp";}catch (UnknownAccountException e){e.printStackTrace();System.out.println("用户名错误");}catch (IncorrectCredentialsException e){e.printStackTrace();System.out.println("秘密错误");}return "redirect:/login.jsp";}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("验证通过");String principal = (String) token.getPrincipal();if("li".equals(principal)){return new SimpleAuthenticationInfo(principal,"123",this.getName());}return null;}

(2)认证功能:

register.jsp:

<h1>用户注册</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">用户名:<input type="text" name="username"><br/>密码:<input type="text" name="password"><br/><input type="submit" value="注册">
</form>

User :

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {private String id;private String username;private String password;private String salt;
}

UserDao:

@Mapper
public interface UserDao {//注册用户void save(User user);//根据用户名查询业务User findUserByName(String username);
}

UserDaoMapper.xml: 

<insert id="save" parameterType="User" keyProperty="id">insert into t_user values ( #{id},#{username},#{password},#{salt} )
</insert><select id="findUserByName" parameterType="String" resultType="User">select id,username,password,salt from t_userwhere username= #{username}
</select>

SaltUtils:

 /*** 生成salt的静态方法* @param n* @return*/public static String getSalt(int n){char[] chars = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm123456789.,;'[]".toCharArray();StringBuffer sb = new StringBuffer();for (int i = 0; i < n; i++) {char char1 = chars[new Random().nextInt(chars.length)];sb.append(char1);//拼接随机字符串}return sb.toString();}

ApplicationContextUtils :

@Component
public class ApplicationContextUtils implements ApplicationContextAware {private static ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.context = applicationContext;}//根据bean的名字获取工厂中指定对象public static Object getBean(String beanName){return context.getBean(beanName);}
}

UserService:

public interface UserService {//注册用户void register(User user);//根据用户名查询业务User findUserByName(String username);
}

UserServiceImpl:

@Service
@Transactional
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic User findUserByName(String username) {return userDao.findUserByName(username);}@Overridepublic void register(User user) {//1.调用saltString salt = SaltUtils.getSalt(8);//2.保存saltuser.setSalt(salt);//3.对密码进行MD5+salt+hash加密Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);user.setPassword(md5Hash.toHex());userDao.save(user);}
}

UserController(下面只粘了用户注册部分的代码):

@Autowired
private UserService userService;/*** 用户注册*/@RequestMapping("register")public String register(User user){try {userService.register(user);return "redirect:/login.jsp";}catch (Exception e){e.printStackTrace();return "redirect:/register.jsp";}}

CustomerRealm: 

 @Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("验证通过");String principal = (String) token.getPrincipal();//在工厂中设置service对象UserService userService = (UserService) ApplicationContext.getBean("userService");User user = userService.findUserByName(principal);if(!ObjectUtils.isEmpty(user)){return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());//注意,user.getSalt()不能获取到salt}return null;}

(3)权限管理

1.基于角色的权限管理:

在index.jsp页面上加上权限控制,加上如下代码:

<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>表示只有admin用户才有权限查看
<shiro:hasRole name="admin"><li><a href="">商品管理</a></li><li><a href="">订单管理</a></li><li><a href="">物流管理</a></li>
</shiro:hasRole>以下表示仅user和admin可见:
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理</a></li>
</shiro:hasAnyRoles>

重写doGetAuthorizationInfo方法:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//获取身份信息String primaryPrincipal = (String) principals.getPrimaryPrincipal();System.out.println("调用授权验证:"+ primaryPrincipal);if("li".equals(primaryPrincipal)){SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addRole("admin");return simpleAuthorizationInfo;}return null;}

2.基于权限修饰符和页面标签的权限管理:

权限字符串的规则:资源标识符:操作:资源实例标识符。

index.jsp(部分代码):

<shiro:hasAnyRoles name="user,admin"><li><a href="">用户管理</a></li><ul><shiro:hasPermission name="user:add:*"><li><a href="">添加用户</a> </li></shiro:hasPermission><shiro:hasPermission name="user:find:*"><li><a href="">查询用户</a> </li></shiro:hasPermission><shiro:hasPermission name="user:update:*"><li><a href="">修改用户</a> </li></shiro:hasPermission><shiro:hasPermission name="user:delete:*"><li><a href="">删除用户</a> </li></shiro:hasPermission></ul></shiro:hasAnyRoles>

在doGetAuthorizationInfo方法里面加上如下代码:

simpleAuthorizationInfo.addStringPermission("user:*:*");

    3.通过编码来实现权限控制:

Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")){
.....
}else{
System.out.println("无权访问");}

    4.通过注解来实现权限控制:

//加上方法前面
@RequiresRoles("admin")//单个角色
@RequiresRoles(value={"admin","user"})//同时具有多个个角色@RequiresPermissions("user:update:01")//用来判断权限字符串

(4)实现EhCache:

Cache的作用:减轻数据库的访问压力,将经常需要访问的数据存入cache中,提高系统查询效率。

引入依赖:

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

 在自定义realm里开启缓存管理 :

customerRealm.setCacheManager(new EhCacheManager());customerRealm.setCachingEnabled(true);//开启全局缓存customerRealm.setAuthenticationCachingEnabled("authenticationCache");//认证缓存customerRealm.setAuthorizationCachingEnabled(true);//开启缓存验证customerRealm.setAuthorizationCacheName("authorizationCache");

(5)结合redis :

引入依赖:

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

启动redis服务器:

redis-server &  //我喜欢这种方式启动,简单粗暴
redis-cli -p 6379

在配置文件里面加上:

spring.redis.port=6379
spring.redis.host=服务器的地址或者localhost  //记得去开放端口号
spring.redis.database=0
 

自定义一个shiro的存储管理器:

public class RedisCacheManager implements CacheManager {//这个参数为认证或者授权的统一名称@Overridepublic <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {return new RedisCache<K,V>(cacheName);}
}

自定义缓存实现:

public class RedisCache<k,v> implements Cache<k,v> {private String cacheName;public RedisCache() {}public RedisCache(String cacheName) {this.cacheName = cacheName;}@Overridepublic v get(k k) throws CacheException {return (v) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());}@Overridepublic v put(k k, v v) throws CacheException {getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);return null;}@Overridepublic v remove(k k) throws CacheException {return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());}@Overridepublic void clear() throws CacheException {getRedisTemplate().opsForHash().delete(this.cacheName);}@Overridepublic int size() {return getRedisTemplate().opsForHash().size(this.cacheName).intValue();}@Overridepublic Set<k> keys() {return getRedisTemplate().opsForHash().keys(this.cacheName);}@Overridepublic Collection<v> values() {return getRedisTemplate().opsForHash().values(this.cacheName);}private RedisTemplate getRedisTemplate(){RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean(redisTemplate);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());return redisTemplate;}
}

然后记得相关实体类要实现序列化,但是还不够,因为ByteSource有点bug,还得

自定义序列化接口:

public class MyByteSource extends SimpleByteSource implements Serializable {public MyByteSource(String string) {super(string);}
}

再将上面的CustomerRealm中的doGetAuthenticationInfo方法下面的ByteSource.Util.bytes(user.getSalt())改成new MyByteSource(user.getSalt())。

(6)加载图形验证码:

在前端页面添加如下代码:

用户验证码:<input type="text"><img src="${pageContext.request.contextPath}/user/getImage" alt=""><br>

验证码生成工具类:

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 {//获取验证码String s = generateVerifyCode(4);//将验证码放入图片中outputImage(260,60,new File("/Users/chenyannan/Desktop/安工资料/aa.jpg"),s);System.out.println(s);}
}

在UserController类中添加如下代码:

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

ps:记得放行。

map.put("/**","authc");
map.put("/user/getImage","anon");

处理身份验证的代码(和之前的有所区别):

 @RequestMapping("login")public String login(String username,String password,String code,HttpSession session){//比较验证码String codes = (String) session.getAttribute("code");try {if(codes.equalsIgnoreCase("code")){//获取主体对象Subject subject = SecurityUtils.getSubject();subject.login( new UsernamePasswordToken(username,password));return "redirect:/index.jsp";}else {throw new RuntimeException("验证码错误!");}}catch (UnknownAccountException e){e.printStackTrace();System.out.println("用户名错误");}catch (IncorrectCredentialsException e){e.printStackTrace();System.out.println("秘密错误");}catch (Exception e){e.printStackTrace();System.out.println(e.getMessage());}return "redirect:/login.jsp";}

修改salt不能序列化后的代码: 

//自定义salt实现  实现序列化接口
public class MyByteSource implements ByteSource,Serializable {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;}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);}}
}

4.用thymeleaf结合

引入依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

配置文件:

server.servlet.context-path=/shiro
# 应用名称
spring.application.name=shirospring.thymeleaf.cache=false
spring.mvc.view.suffix=.html
spring.mvc.view.prefix=classpath:/templates/spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456mybatis.type-aliases-package=com.li.thymeleaf.entity
mybatis.mapper-locations=classpath:com/li/mapper/*.xmlspring.redis.port=6379
spring.redis.host=39.108.178.137:22
spring.redis.database=0

测试框架是否搭建成功:

@Controller
@RequestMapping("hello")
public class HelloController {@RequestMapping("hello")public String hello(){System.out.println("thymeleaf");return "index";}
}

然后把之前的前端.jsp页面改为.html,记得使用thymeleaf语法,再把重定向的路径改了,把login.html设为公共资源。

最新版Shiro-SpringBoot项目实战笔记相关推荐

  1. springboot项目实战_2019学习进阶之路:高并发+性能优化+Spring boot等大型项目实战...

    Java架构师主要需要做哪些工作呢? 负责设计和搭建软件系统架构(平台.数据库.接口和应用架构等),解决开发中各种系统架构问题. 优化现有系统的性能,解决软件系统平台关键技术问题攻关.核心功能模块设计 ...

  2. Vue整合SpringBoot项目实战之Vue+Element-Ui搭建前端项目

    Vue整合SpringBoot项目实战之Vue+Element-Ui搭建前端项目 源码(欢迎star): 前端项目代码 后端项目代码 系列文章: Vue整合SpringBoot项目实战之后端业务处理 ...

  3. Unity3D项目实战笔记(10):Unity3D编译IPA的PostEvents–节约时间利器

    最近,SDK支付等接入差不多了,就从Unity3D生成IPA (企业版License), 然,需要手动执行的PostEvents竟然多大10项+, 这些我默默的承受了1周时间,每次约浪费20分钟-额外 ...

  4. springboot 项目实战 基本框架搭建(IDEA)

    springboot 项目实战 基本框架搭建(IDEA) IDEA下载 我使用的是破解的专业版IDEA,使用权一直到2089年: 下载IDEA: 下载processional版本,然后百度搜索激活码即 ...

  5. 【SpringBoot项目实战】图片压缩包上传、解压、存储等等一套流程教学

    [SpringBoot项目实战]图片压缩包上传.解压.存储等等一套流程教学 前言 一.压缩包上传 1.接口实现 2.获取压缩包的文件名和文件路径 二.压缩包解压并保存 1.处理压缩包文件方法 解压缩步 ...

  6. 1.vue项目实战笔记(已完结)

    vue项目实战笔记 目标 目录 1.项目概述 1.1电商项目基本业务概述 1.2电商后台管理系统的功能 1.3电商后台管理系统的开发模式(前后端分离) 1.4电商后台管理系统的技术选型 1.前端项目技 ...

  7. Shiro教程,整合SpringBoot项目实战(笔记)

    1.shiro 1.1什么是权限管理 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己 ...

  8. 备战金九银十,阿里P8师兄指导完整攻略(附:学习资料+面试宝典+项目实战笔记)

    前言 还剩下两个月就到了金九银十,一般来说,秋招的含金量明显是高于春招的. 那么如何准备即将到来的面试热潮呢?运筹帷幄之后,决胜千里之外! 坚决不打毫无准备的仗是小编的原则:不论是笔试还是面试都是有章 ...

  9. android项目实战博学谷源码_阿里爆款SpringBoot项目实战PDF+源码+视频分享

    前言 关于SpringBoot网络上有太多的博客跟资料,其影响力想必不用我多说了.它作为当前最流行的微服务框架,不但使用更加简单,而且功能更加丰富.性能更加稳定和健壮.其"约定大于配置&qu ...

  10. SpringBoot项目实战:员工管理系统

    员工管理系统 该项目实现了以下功能,对应的源码与数据库文件在资源中可下载 项目部分展示 1. 数据库设计 创建数据库 employee,创建两个表 employee(员工表)和depart(部门表) ...

最新文章

  1. apu和atom处理器两者的定位
  2. codeforces831c 思维
  3. NCEMASLG-32G的说明
  4. css为网页顶部和底部都加入背景图
  5. jar构建docker镜像_dockerfile构建docker镜像详细说明,主要是springboot的jar包构建镜像样例...
  6. WebBrowser控件禁用超链接转向、脚本错误提示、默认右键菜单和快捷键
  7. 欣赏下国外人css3打造的载入动画
  8. Ceph rbd cmd练习
  9. js里css不起作用,CSS文件在NODE js中不起作用
  10. oracle rpad()函数
  11. 基于matlab的科学计算器设计,MATLAB科学计算器设计
  12. 记录FinalShell退格键
  13. iOS 跳转淘宝、天猫、京东商品详情页
  14. 歌尔首次闪耀CES Asia,展示全面创新力量
  15. 服务器CPU占用率高,如何排查?
  16. Mysql中自定义函数的创建和执行
  17. 云产品经理相关技术知识(一)
  18. 手把手搭建SSM框架
  19. 超声在肝脏扫描15cm深度时的PRF应该用多大的值?
  20. MaxCompute快速入门

热门文章

  1. 如何打造一个让人愉快的框架
  2. elementUI之表格排序失效,表格宽度可拖拽变宽变窄
  3. 关于 国产麒麟系统Qt强制退出应用程序qApp->exit()无效 的解决方法
  4. 小程序运营打包 遇到找不到game.json
  5. 2019 icpc南昌邀请赛 G Winner
  6. 【CSS】关于 z-index,你可能一直存在误区
  7. 计算机中丢失ucore46.dll,Creo6.0 Purge功能 如果试过各种方法还不能使用,可以试下这个方法...
  8. RCNN算法思想简单讲解概述————(究极简单的讲述和理解)
  9. 计算机科学与技术专业成功人士,我校2002级计算机科学与技术专业校友重返母校...
  10. Java读写txt文件案例-统计学生名单