1.shiro
Apache Shiro是一个强大且易用的Java安全框架。
shiro四大基石–身份验证,授权,会话管理,加密。
Authentication(身份认证):即我们平时所说的“登录”。
2.整合
2.1 整合说明
在spring项目中,都是通过spring来管理bean,如果想要使用shiro,就需要将shiro整合到spring。集成Spring的核心就是把框架的核心类(Subject,SecurityManager,Realm)交给Spring管理。
2.2整合步骤
- 在pom.xml中导入shiro-all
<!-- 日志slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<!-- shiro-all -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
- 在/WEB-INF/web.xml中配置shiro的过滤器
<!--配置shiro的过滤器-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 在resources下创建spring-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建域,主要是处理用户认证-->
<bean id="authRealm" class="com.sunyue.ssm.web.shiro.realm.AuthRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--配置加密方式,必须与注册时候加密方式一致-->
<property name="hashAlgorithmName" value="MD5"/>
<!--配置加密次数,必须与注册时候加密次数一致-->
<property name="hashIterations" value="3306"/>
</bean>
</property>
</bean>
<!--创建安全认证管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="authRealm"></property>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<!--对登录的资源路径放行,因为使用了过滤器,所以原则上来说,所有资源全部访问不了-->
<property name="loginUrl" value="/login.jsp"></property>
<!--对没有权限时默认的界面放行,比如错误界面-->
<property name="unauthorizedUrl" value="error.jsp"></property>
<!--对认证成功也就是登录成功之后的资源放行,一般配置后台的主界面路径-->
<!--<property name="successUrl" value=""></property>-->
<property name="filterChainDefinitions">
<value>
<!--对静态资源不拦截-->
/static/*=anon <!--以后,css,js,bootstrap,vue,layui等静态资源全部放在static目录下-->
/user/login=anon <!--对登录的后台action进行放行-->
<!--配置/user/list.do只有有user权限的用户才能访问
/user/list.do=perms[user:list]
-->
<!--声明/user/logout.do使用logout的过滤器
该过滤器会自动帮我们清除退出登录时要清除的东西
-->
/user/logout=logout
<!--以下资源进行拦截-->
/*=authc
/*/*=authc
</value>
</property>
</bean>
</beans>
代码
- 结构
- User.java
package com.sunyue.ssm.bean;
import java.util.Date;
public class User {
private int id;
private String username;
private String password;
private String gender;
private String phone;
private Date birthday;
private String pic;
private int delFlag;
public User() {
}
public User(String username) {
this.username = username;
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getPic() {
return pic;
}
public void setPic(String pic) {
this.pic = pic;
}
public int getDelFlag() {
return delFlag;
}
public void setDelFlag(int delFlag) {
this.delFlag = delFlag;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", phone='" + phone + '\'' +
", birthday=" + birthday +
", pic='" + pic + '\'' +
", delFlag=" + delFlag +
'}';
}
}
- UserDao.java
package com.sunyue.ssm.dao;
import com.sunyue.ssm.bean.User;
public interface UserDao {
/**
* 登录
* @param user
* @return
*/
public User login(User user);
/**
* 注册(加密)
* @param user
* @return
*/
public int saveUser(User user);
}
- UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sunyue.ssm.dao.UserDao">
<resultMap id="findUserResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="gender" property="gender"/>
<result column="phone" property="phone"/>
<result column="birthday" property="birthday"/>
<result property="pic" column="pic"/>
</resultMap>
<!--登录-->
<select id="login" parameterType="User" resultMap="findUserResultMap">
select * from users where username=#{username}
</select>
<insert id="saveUser" parameterType="User">
insert into users(username,password) values (#{username},#{password})
</insert>
</mapper>
- IUserService.java
package com.sunyue.ssm.service;
import com.sunyue.ssm.bean.User;
public interface IUserService {
/**
* 用户登录
* @param user
* @return
* @throws Exception
*/
public User login(User user)throws Exception;
/**
* 注册(加密)
* @param user
* @return
*/
public int saveUser(User user)throws Exception;
}
- UserService.java
package com.sunyue.ssm.service.impl;
import com.sunyue.ssm.bean.User;
import com.sunyue.ssm.dao.UserDao;
import com.sunyue.ssm.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(value = "transactionManager")
public class UserService implements IUserService {
@Autowired
private UserDao userDao;
/**
* 用户登录
*
* @param user
* @return
* @throws Exception
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
@Override
public User login(User user) throws Exception {
return userDao.login(user);
}
/**
* 注册(加密)
*
* @param user
* @return
*/
@Override
public int saveUser(User user) throws Exception {
return userDao.saveUser(user);
}
}
- UserTest.java
package com.sunyue.ssm.test;
import com.sunyue.ssm.bean.User;
import com.sunyue.ssm.service.IUserService;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-bean.xml")
public class UserTest {
@Autowired
private IUserService userService;
@Test
public void test1() throws Exception {
//shiro的加密:MD5加密方式+盐值+加密次数
//需要进行MD5加密
String algorithmName ="MD5";//加密方式
Object source ="111";//这是加密源,也就是需要加密的密码
//为了提高加密的安全性,或者说复杂度,进行加盐值,随便,要求加密和认证的时候的盐值一样
ByteSource salt = ByteSource.Util.bytes("十里长街千堆雪");
//为了进一步提高加密的复杂度,进行加密次数的设置
int hashIterations = 3306;
//进行加密
SimpleHash sh = new SimpleHash(algorithmName,source,salt,hashIterations);
System.out.println("加密前加密后密码对比----加密前:111,加密后:"+sh.toString());
User users = new User("pomelo",sh.toString());//加密后的密码
int i = userService.saveUser(users);
System.out.println(i);
}
}
测试结果:
查看数据库是否成功创建用户且加密密码:
- UserController.java
package com.sunyue.ssm.controller;
import com.sunyue.ssm.bean.User;
import com.sunyue.ssm.service.IUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("/user")
public class UserController {
private static final transient Logger log = LoggerFactory.getLogger(UserController.class);
@Autowired
private IUserService userService;
/**
* 登录
* @param user
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(User user,HttpSession session){
log.info("===================>UserController.java-->login() start");
//创建Subject
Subject currentUser = SecurityUtils.getSubject();
//将用户名和密码封装到token中
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
if (!currentUser.isAuthenticated()){//未登录
log.info("==========>UserController.java-->login()-->currentUser.isAuthenticated()");
try {
//使用subject提供的login方法进行认证
log.info("============>UserController.java-->login()-->currentUser.login(token) start");
currentUser.login(token);
log.info("============>UserController.java-->login()-->currentUser.login(token) end");
log.info("登录成功"+token.getPrincipal());
return "redirect:/good/showGood";
} catch (UnknownAccountException uae) {
log.info("没有用户名为的用户" + token.getPrincipal());
return "redirect:/login.jsp";
}
// 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。
catch (IncorrectCredentialsException ice) {
log.info("密码帐户 " + token.getPrincipal() + " 是不正确的!");
return "redirect:/login.jsp";
}
// 用户被锁定的异常 LockedAccountException
catch (LockedAccountException lae) {
log.info("用户名账户 " + token.getPrincipal() + "被锁定. " + "请联系管理员解锁.");
return "redirect:/login.jsp";
}catch (AuthenticationException e){
log.info("认证失败 ");
return "redirect:/login.jsp";
}
}
log.info("------userController.java-->login() end");
log.info("登录失败!");
return "redirect:/login.jsp";
}
}
- AuthRealm.java
package com.sunyue.ssm.web.shiro.realm;
import com.sunyue.ssm.bean.User;
import com.sunyue.ssm.service.IUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 主要是进行登录的验证
*/
public class AuthRealm extends AuthenticatingRealm {
@Autowired
private IUserService userService;
/**
* 进行登录验证
* @param token (这个token就是UserController中封装的UsernamePasswordToken)
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("=====>AuthRealm.java-->doGetAuthenticationInfo()");
UsernamePasswordToken upt = (UsernamePasswordToken) token;
//获取登录的用户名
String username = upt.getUsername();//前台的用户名
try {
//通过数据库进行验证
User u = userService.login(new User(username));//封装要认证的数据
Object principal = u.getUsername();//需要认证的身份:主体:用户名进行认证(用户名来自数据库)
Object credentials = u.getPassword();//认证的凭证:就是我们的密码(密码来自数据库)
ByteSource credentialsSalt = ByteSource.Util.bytes("十里长街千堆雪");
return new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,getName());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
至此,登录加密已经实现。
现在登录,就只能用pomelo这个账号进行登录,而admin这个账号因为密码未进行加密处理,而登录从前台传过来的密码是进行加密过的,与数据库中的不匹配,所以admin就不能登录成功。
前台页面见:https://blog.csdn.net/SUNxiaodui/article/details/108584955(SSM:Spring+SpringMVC+MyBatis整合之登录+商品信息分页展示(IDEA))