您的位置:首页 > 编程语言 > Java开发

SpringBoot/SpringMVC整合Shiro(一):实现登录与注册(MD5加盐加密)

2018-01-07 02:15 1311 查看
本文欢迎转载,转载前请联系作者,经允许后方可转载。转载后请注明出处,谢谢! http://blog.csdn.net/colton_null 作者:喝酒不骑马 Colton_Null from CSDN

终于终于,用正确姿势搭建了一个Spring框架下整合Shiro的登录注册的DEMO。

因为还有其他事情要忙,所以有关这个shiro的demo前前后后鼓捣了一周多。为啥多花这么多时间?

按照平常,我在研究新框架的时候,都是看看框架介绍,找找样例几天就能搞出来了。但这次最气的是,目前能够讲清楚Spring + Shiro整合方法认真写的博客几乎没有。照着几个博客跟着配置,越配越生气的。给的配置文件不全;夹杂着好多没用的配置文件;没有注释;代码不全;还有的用法直接就是错的。耽误了太多的时间。

所以,就自己整理下正确实现Spirng + Shiro整合的姿势。分享给大家,希望别再走弯路。文末有demo源码,大家可以自行下载。

本文主要基于SpringBoot + Maven + Mybatis实现对Shiro的整合。对于Mabatis的配置在这里就不多做介绍了。而对于SpringMVC,也是SSM框架,只不过在对Shiro的配置方式上有所不同,原理都是一样的。我也会把SpringMVC下Shiro的配置方法贴出来。

在这里推荐两个个人觉得受益颇多的教程链接:

《跟我学shiro》

腾讯课程视频《Shiro安全框架》

一、准备好Maven + SpringBoot + MyBatis开发环境

1.准备一个user_t表,用户存放账户信息

-- ----------------------------
-- Table structure for user_t
-- ----------------------------
DROP TABLE IF EXISTS `user_t`;
CREATE TABLE `user_t` (
`id` varchar(32) NOT NULL,
`username` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS=1;


2.编写两个DAO接口

分别用于根据username查询信息以及插入一条数据。

UserMapper.java

@Repository
public interface UserMapper {

/**
* 根据用户名查询用户信息
* @param username 用户名
* @return 将数据封装到Map类型中
*/
public Map<String, Object> queryInfoByUsername(String username);

/**
* 插入一条数据
* @param data Map中包含id,username,password
*/
public void insertData(Map<String, String> data);
}


userMapper.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.myz.shirodemo.dao.UserMapper">

<select id="queryInfoByUsername" parameterType="java.lang.String" resultType="java.util.Map">
SELECT id, username, password FROM user_t WHERE username = #{username,jdbcType=VARCHAR}
</select>

<insert id="insertData"  parameterType="java.util.Map">
INSERT INTO user_t ( id, username,password )
VALUES ( #{id, jdbcType=VARCHAR}, #{username, jdbcType=VARCHAR},#{password, jdbcType=VARCHAR});
</insert>
</mapper>


二、引入Shiro依赖

pom中有关shiro的依赖如下

<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>

<dependency>
<groupId>org.a
4000
pache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.2</version>
</dependency>

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

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
<!-- shiro END-->


三、配置ShiroConfig类

ShiroConfig.java

@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器。匹配原则是最上面的最优先匹配
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/doLogin", "anon");
filterChainDefinitionMap.put("/doRegister", "anon");
filterChainDefinitionMap.put("/register", "anon");

// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/doLogout", "logout");

// 剩余请求需要身份认证
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");

// 未授权界面;
//        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}

@Bean(name = "myShiroRealm")
public ShiroRealm myShiroRealm(HashedCredentialsMatcher matcher){
ShiroRealm myShiroRealm = new ShiroRealm();
myShiroRealm.setCredentialsMatcher(matcher);
return myShiroRealm;
}

@Bean
public SecurityManager securityManager(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm(matcher));
return securityManager;
}

/**
* 密码匹配凭证管理器
*
* @return
*/
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 采用MD5方式加密
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 设置加密次数
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
}


1.shirFilter(SecurityManager securityManager)方法,是设置shiro的过滤规则。用于控制哪些请求需要身份认证后才能继续执行,哪些不需要认证等。

http://blog.csdn.net/u010092167/article/details/52372811这个博主的博文中找到拦截器汇总表。

身份验证相关:

authc:基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址;failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure);

authcBasic:Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);

logout:退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout”

user:用户拦截器,用户已经身份验证/记住我登录的都可;示例“/**=user”

anon:匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/**=anon”

授权相关的:

roles:角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]”

perms:权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms[“user:create”]”

port:端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样

rest:rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll);

ssl:SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;

其他:

noSessionCreation:不创建会话拦截器,调用 subject.getSession(false)不会有什么问题,但是如果subject.getSession(true)将抛出 DisabledSessionException异常;

2.myShiroRealm(HashedCredentialsMatcher matcher)用于配置自定义的Realm。在Shiro中,所有有关身份认证及授权管理数据源的获取与管理,都在Realm中进行。

3.hashedCredentialsMatcher()用于生成加密规则。这里采用MD5加密1024次的方式对密码进行加密处理。

4.securityManager(HashedCredentialsMatcher matcher)将加密规则属性设置到自定义的ShiroRealm中,并将这个Realm加载到SecurityManager中。

四、配置自定义Realm

ShiroRealm.java

public class ShiroRealm extends AuthenticatingRealm {
@Autowired
private BaseService baseService;

private SimpleAuthenticationInfo info = null;

/**
* 1.doGetAuthenticationInfo,获取认证消息,如果数据库中没有数,返回null,如果得到了正确的用户名和密码,
* 返回指定类型的对象
*
* 2.AuthenticationInfo 可以使用SimpleAuthenticationInfo实现类,封装正确的用户名和密码。
*
* 3.token参数 就是我们需要认证的token
* @param authenticationToken
* @return
* @throws AuthenticationException
*/

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 将token装换成UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
// 获取用户名即可
String username = upToken.getUsername();
// 查询数据库,是否查询到用户名和密码的用户
Map<String, Object> userInfo = baseService.queryInfoByUsername(username);

if(userInfo != null) {
// 如果查询到了,封装查询结果,返回给我们的调用
Object principal =  userInfo.get("username");
Object credentials = userInfo.get("password");

// 获取盐值,即用户名
ByteSource salt = ByteSource.Util.bytes(username);
String realmName = this.getName();
// 将账户名,密码,盐值,realmName实例化到SimpleAuthenticationInfo中交给Shiro来管理
info = new SimpleAuthenticationInfo(principal, credentials, salt,realmName);
}else {
// 如果没有查询到,抛出一个异常
throw new AuthenticationException();
}
return info;
}
}


1.这里我只做了身份认证。新建一个ShiroRealm类继承AuthenticatingRealm类,实现doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法。

2.这个方法主要就是用于获取数据库中的账户信息,以便用于和用户登录时从前台传过来的账户密码进行对比。

3.根据用户名到用户表中查询账户名密码,并设置好盐值。这里的盐值要和ShiroConfig中的盐值规则一样。将账户名,密码,盐值,realmName实例化到SimpleAuthenticationInfo中交给Shiro来管理。

4.如果账户不存在,则抛出AuthenticationException异常。

5.这样,每次用户进行login操作时,就会调用doGetAuthenticationInfo方法。Shiro就自动帮我们校验了账户密码是否匹配。

五、实现登录

这里先贴上Controller的全部代码

MyController.java

@Controller
public class MyController {
@Autowired
private BaseService baseService;

private final Logger logger = LoggerFactory.getLogger(MyController.class);

@RequestMapping("/doLogin")
public String doLogin(@RequestParam("username") String username,
@RequestParam("password") String password) {
// 创建Subject实例
Subject currentUser = SecurityUtils.getSubject();

// 将用户名及密码封装到UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(username, password);

try {
currentUser.login(token);
// 判断当前用户是否登录
if (currentUser.isAuthenticated() == true) {
return "/index.html";
}
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("登录失败");
}
return "/loginPage.html";
}

@RequestMapping("/doRegister")
public String doRegister(@RequestParam("username") String username,
@RequestParam("password") String password) {
boolean result = baseService.registerData(username,
10cb2
password);
if(result){
return "/login";
}
return "/register";
}

@RequestMapping(value = "/login")
public String login() {
logger.info("login() 方法被调用");
return "loginPage.html";
}

@RequestMapping(value = "/register")
public String register() {
logger.info("register() 方法被调用");
return "registerPage.html";
}

@RequestMapping(value = "/hello")
public String hello() {
logger.info("hello() 方法被调用");
return "helloPage.html";
}
}


1.在doLogin方法中,实现登录认证过程。

2.首先获取当前Subject实例

3.将用户名和密码封装到UsernamePasswordToken中

4.用当前Subject实例执行login方法,传入参数为刚刚封装的token。执行login方法后,shiro框架最终就会调用刚刚自定义ShiroRealm中的doGetAuthenticationInfo方法。

5.用isAuthenticated()方法判断用户是否已经登录,如果是则跳转到登录后的页面(这里我跳转到的是index.html)。如果登录失败,则走报异常,最后还是跳转到登录界面。

6.这里我只catch了AuthenticationException异常。然而在AuthenticationException下有多个子异常,用于各种登录失败的场景,比如账户名不存在,密码不对,登录次数过多等等。大家针对不同的情况做不同的处理。但有一点建议,就是对于前台用户来说,不要暴露过多的错误信息,只是报一个登录失败即可,提高安全性。

六、实现注册

在Controller的doRegister方法中,调用service层的registerData方法,完成注册功能。

在service中对DAO进行封装,实现信息查询以及信息注册。

接口BaseService.java

public interface BaseService {

/**
* 根据用户名查询用户信息
* @param username 用户名
* @return 将数据封装到Map类型中
*/
public Map<String, Object> queryInfoByUsername(String username);

/**
* 注册功能
* @param username 用户名
* @param password 密码
* @return
*/
public boolean registerData(String username, String password);
}


BaseService接口的实现类

BaseServiceImpl.java

@Service
public class BaseServiceImpl implements BaseService {
@Autowired
private UserMapper userMapper;

@Override
public Map<String, Object> queryInfoByUsername(String username) {
return userMapper.queryInfoByUsername(username);
}

@Override
public boolean registerData(String username, String password) {
// 生成uuid
String id = UUIDUtil.getOneUUID();

// 将用户名作为盐值
ByteSource salt = ByteSource.Util.bytes(username);
/*
* MD5加密:
* 使用SimpleHash类对原始密码进行加密。
* 第一个参数代表使用MD5方式加密
* 第二个参数为原始密码
* 第三个参数为盐值,即用户名
* 第四个参数为加密次数
* 最后用toHex()方法将加密后的密码转成String
* */
String newPs = new SimpleHash("MD5", password, salt, 1024).toHex();

Map<String, String> dataMap = new HashMap<>();
dataMap.put("id", id);
dataMap.put("username", username);
dataMap.put("password", newPs);

// 看数据库中是否存在该账户
Map<String, Object> userInfo = queryInfoByUsername(username);
if(userInfo == null) {
userMapper.insertData(dataMap);
return true;
}
return false;
}
}


1.注册时注意,由于之前配置了盐值规则及加密规则,所以这里要对用户输入的密码也做相同的处理之后再存入数据库中。

2.使用SimpleHash类完成密码的加密。最后用toHex()将加密后的密码转成String。

七、大功告成

到此,有关SpringBoot与Shiro的整合就基本完成了。有关静态页的代码这里就不贴了,在下面的github源码中有,大家可以自行下载。

最终效果就是,在http://localhost/register界面中,完成账户的注册。在http://localhost/login界面中进行登录操作。如果登录成功,则跳转到index页。如果登录失败或者没有登录,则访问index时会自动跳转到login界面。

八、有关SpringMVC环境下的配置

在SpringMVC下,有关Shiro的配置就要在Spring配置文件中来完成。

配置原理和SpringBoot中的一样。

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="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/views/success.jsp"/>
<property name="unauthorizedUrl" value="/views/test.jsp"/>
<!-- 配置拦截策略 -->
<property name="filterChainDefinitions">
<value>
/login = anon
/doLogin = anon
/doLogout = logout
/** = authc
</value>
</property>
</bean>

<!-- 自定义Realm -->
<bean id="myRealm" class="com.myz.bean.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>

<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 缓存管理器 -->
<property name="cacheManager" ref="cacheManager" />
<property name="realm" ref="myRealm" />
</bean>

<!-- 配置ehcache -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
</bean>

<!-- 用来管理Spring容器中的Shiro常见的对象 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>

<!-- 网络方面 -->
<bean id="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
<property name="securityManager" ref="securityManager"/>
</bean>

</beans>


shiro缓存配置

ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false"  name="shirocache">

<diskStore path="java.io.tmpdir"/>

<!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro_cache"
maxElementsInMemory="2000"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
maxElementsOnDisk="0"
overflowToDisk="true"
memoryStoreEvictionPolicy="FIFO"
statistics="true">
</cache>
</ehcache>


九、源码

有关SpringBoot + Maven + Mybatis + Shiro的登录注册demo源码:github–>shirodemo
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  shiro springboot springmvc