本篇所涉及的源码:
gitee.com源码地址:https://gitee.com/zhenqi/qiucodeBlog
github.com源码地址:https://github.com/zhenqicai/qiucodeBlog
一、在pom.xml文件中添加Shiro的依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.4.0version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
二、编写Shiro的配置类
package cn.qiucode.config;
import cn.qiucode.shiro.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* @author 上官江北
* @date 2019/7/21 20:52
**/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/admin/**", "anon");
filterChainDefinitionMap.put("/editormd/**", "anon");
filterChainDefinitionMap.put("/front/**", "anon");
filterChainDefinitionMap.put("/layout/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//过滤链定义,从上向下顺序执行,一般将放在最为下边这是一个坑呢,一不小心代码就不好使了;
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/admin/**", "authc");
filterChainDefinitionMap.put("/menu/**", "authc");
filterChainDefinitionMap.put("/system/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/admin/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
// hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
userRealm.setCachingEnabled(true);
//启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
userRealm.setAuthenticationCachingEnabled(false);
//缓存AuthenticationInfo信息的缓存名称
userRealm.setAuthenticationCacheName("authenticationCache");
//启用授权缓存,即缓存AuthorizationInfo信息,默认false
userRealm.setAuthorizationCachingEnabled(true);
//缓存AuthorizationInfo信息的缓存名称
userRealm.setAuthorizationCacheName("authorizationCache");
return userRealm;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//自定义realm
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public SimpleCookie cookie() {
SimpleCookie cookie = new SimpleCookie("QIUSESSIONID"); // cookie的name,对应的默认是 JSESSIONID
cookie.setHttpOnly(true);
cookie.setMaxAge(-1);
cookie.setPath("/"); // path为 / 用于多个系统共享JSESSIONID
return cookie;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//单位是毫秒
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setGlobalSessionTimeout(60 * 30 * 1000);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationInterval(60 * 30 * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookie(cookie());
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
/**
* 解决:无权限页面不跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized") 无效
* shiro的源代码ShiroFilterFactoryBean.Java定义的filter必须满足filter instanceof AuthorizationFilter,
* 只有perms,roles,ssl,rest,port才是属于AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter,
* 所以unauthorizedUrl设置后页面不跳转 Shiro注解模式下,登录失败与没有权限都是通过抛出异常。
* 并且默认并没有去处理或者捕获这些异常。在SpringMVC下需要配置捕获相应异常来通知用户信息
* @return
*/
@Bean(name = "simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
// mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException", "/admin/403");
r.setExceptionMappings(mappings); // None by default
r.setExceptionAttribute("exception"); // Default is "exception"
return r;
}
}
三、编写Shiro的Realm类
package cn.qiucode.shiro;
import cn.qiucode.entity.AdminUser;
import cn.qiucode.service.AdminUserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author 上官江北
* @date 2019/7/21 21:17
**/
public class UserRealm extends AuthorizingRealm {
@Autowired
private AdminUserService adminUserService;
//权限信息,包括角色以及权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//如果授权部分没有传入User对象,这里只能取到userName
//也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要AdminUser对象
AdminUser user = (AdminUser) principals.getPrimaryPrincipal();
return authorizationInfo;
}
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//获取用户的输入的账号.
String userName = (String) token.getPrincipal();
if (userName == null)
return null;
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
AdminUser user=adminUserService.findByName(userName);
if (user == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //这里传入的是user对象,比对的是用户名,直接传入用户名也没错,但是在授权部分就需要自己重新从数据库里取权限
user.getPassword(), //密码
ByteSource.Util.bytes("admin123456"),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
}
四、编写相应的Service接口和实现类
package cn.qiucode.service;
import java.util.Map;
/**
* @author 上官江北
* @date 2019/7/22 21:20
**/
public interface LoginService {
public Map<String,Object> login(String userName, String password);
public void logout();
}
package cn.qiucode.service.impl;
import cn.qiucode.service.LoginService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* @author 上官江北
* @date 2019/7/22 21:25
**/
@Service("loginService")
public class LoginServiceImpl implements LoginService {
@Override
public Map<String, Object> login(String userName, String password) {
Map<String,Object> result=new HashMap<>();
if(StringUtils.isBlank(userName)){
result.put("message","用户名为空");
result.put("success",false);
return result;
}
String msg="";
// 1、获取Subject实例对象
Subject currentUser = SecurityUtils.getSubject();
// 2、将用户名和密码封装到UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try{
currentUser.login(token);// 传到MyAuthorizingRealm类中的方法进行认证
Session session = currentUser.getSession();
session.setAttribute("userName", userName);
result.put("message","登录成功!");
result.put("success",true);
return result;
}catch (UnknownAccountException e){
e.printStackTrace();
msg="账号不存在!";
} catch (AuthenticationException e) {
e.printStackTrace();
msg="用户验证失败!";
}
result.put("message",msg);
result.put("success",false);
return result;
}
@Override
public void logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
}
五、在AdminUserService接口和实现类添加方法
//AdminUserService接口
public AdminUser findByName(String username);
//AdminUserService实现类
@Override
public AdminUser findByName(String username){
return adminUserDao.findByName(username);
}
//AdminUserDao接口
public AdminUser findByName(String username);
<select id="findByName" resultType="cn.qiucode.entity.AdminUser">
select * from qiu_admin where username=#{username} and status=1
select>
六、修改LoginController
package cn.qiucode.controller.admin;
import cn.qiucode.entity.AdminUser;
import cn.qiucode.service.AdminUserService;
import cn.qiucode.service.LoginService;
import cn.qiucode.utils.AesUtils;
import cn.qiucode.utils.MD5util;
import cn.qiucode.utils.RandomUtils;
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 org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* Created by wuming on 2018/8/25.
*/
@Controller
public class LoginController {
@Autowired
private AdminUserService adminUserService;
@Autowired
private LoginService loginService;
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String toLogin(Map<String, Object> map, HttpServletRequest request){
loginService.logout();
String key = RandomUtils.generateString(16);
map.put("key",key);
return "front/login";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public Map<String,Object> login(String username, String password,String key){
Map<String,Object> res=new HashMap<String,Object>();
password= AesUtils.decrypt(password,key);//MD5util.md5(password);
Map<String,Object> loginResult=loginService.login(username, password);
boolean flag=(boolean)loginResult.get("success");
if(flag){
res.put("code",1);
res.put("msg","登录成功!");
res.put("url","/admin/index");
}else{
res.put("code",-1);
res.put("msg","账号密码不正确,登录失败!");
}
return res;
}
@RequestMapping("/admin/index")
public String index(){
return "admin/index";
}
@RequestMapping("/logout")
public String logOut(HttpSession session) {
loginService.logout();
return "front/login";
}
}