一、我的项目是多模块,这里shiro单独一个模块,下面的三个有用到的类
二、添加依赖包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
三、新建ShiroConfiguration.java(基本网上copy,自己根据需要修改了点东西)
@Configuration
public class ShiroConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em;
}
@Bean(name = "myShiroRealm")
public MyShiroRealm myShiroRealm(EhCacheManager cacheManager) {
MyShiroRealm realm = new MyShiroRealm();
realm.setCacheManager(cacheManager);
return realm;
}
/**
* 注册DelegatingFilterProxy(Shiro)
* 集成Shiro有2种方法:
* 1. 按这个方法自己组装一个FilterRegistrationBean(这种方法更为灵活,可以自己定义UrlPattern,
* 在项目使用中你可能会因为一些很但疼的问题最后采用它, 想使用它你可能需要看官网或者已经很了解Shiro的处理原理了)
* 2. 直接使用ShiroFilterFactoryBean(这种方法比较简单,其内部对ShiroFilter做了组装工作,无法自己定义UrlPattern,
* 默认拦截 /*)
*/
// @Bean
// public FilterRegistrationBean filterRegistrationBean() {
// FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
// filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
// // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
// filterRegistration.addInitParameter("targetFilterLifecycle", "true");
// filterRegistration.setEnabled(true);
// filterRegistration.addUrlPatterns("/*");// 可以自己灵活的定义很多,避免一些根本不需要被Shiro处理的请求被包含进来
// return filterRegistration;
// }
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroRealm myShiroRealm) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(myShiroRealm);
// <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
dwsm.setCacheManager(getEhCacheManager());
return dwsm;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
/**
* 加载shiroFilter权限控制规则(从数据库读取然后配置)
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){/*, UserService userService*/
/////////////////////// 下面这些规则配置最好配置到配置文件中 ///////////////////////
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//自定义拦截器 验证码使用
//Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
//filters.put("captchaVaildate", new CaptchaValidateFilter());
//filters.put("authc111", new MyFormAuthenticationFilter());
// authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
// anon:它对应的过滤器里面是空的,什么都没做
logger.info("##################从数据库读取权限规则,加载到shiroFilter中##################");
//使用自定义拦截器
filterChainDefinitionMap.put("/sys/logining", "captchaVaildate");
//filterChainDefinitionMap.put("/sys/main", "authc,roles[admin]");//这里跳转方法时候会需要权限校验
// filterChainDefinitionMap.put("/sysUser/queryUser", "authc,roles[admin]");//这里跳转方法时候会需要权限校验(是否有角色),无权限时候跳转/sys/403
filterChainDefinitionMap.put("/sys/main", "authc");
filterChainDefinitionMap.put("/sys/403", "anon");
filterChainDefinitionMap.put("/sys/logout", "anon");
filterChainDefinitionMap.put("/sysUser/**", "authc");
filterChainDefinitionMap.put("/**", "anon");//anon 可以理解为不拦截
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/sys/login");//未登录时候跳转
// 登录成功后要跳转的连接
shiroFilterFactoryBean.setSuccessUrl("/sys/main");
shiroFilterFactoryBean.setUnauthorizedUrl("/sys/403");//无权限时候跳转
loadShiroFilterChain(shiroFilterFactoryBean);/*, userService*/
return shiroFilterFactoryBean;
}
}
四、新建 MyShiroRealm.java
public class MyShiroRealm extends AuthorizingRealm{
private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
private ISysUserService iSysUserService;
@Autowired
private SysUserMapper sysUserMapper;
/**
* 权限认证,为当前登录的Subject授予角色和权限
* 本例中该方法的调用时机为需授权资源被访问时
* 并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
* 如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("##################执行Shiro权限认证1111##################");
//获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next();
String loginName = (String)super.getAvailablePrincipal(principalCollection);
//到数据库查是否有此对象
QueryWrapper<SysUser> wrapper = new QueryWrapper<SysUser>();
wrapper.eq("user_name",loginName);
SysUser sysUser=sysUserMapper.selectOne(wrapper);// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
if(sysUser!=null){
//权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//用户的角色集合
// info.setRoles(user.getSysRolesName());
Set<String> roles=new HashSet<String>();
List<Map<String,Object>> mapList=iSysUserService.selectUserRoleById(sysUser.getId());
for (Map<String, Object> map : mapList) {
for (Map.Entry<String, Object> m : map.entrySet()) {
roles.add(m.getValue().toString());
}
}
info.addRoles(roles); //角色
return info;
}
// 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
return null;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken) throws AuthenticationException {
//UsernamePasswordToken对象用来存放提交的登录信息
UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
logger.info("验证当前Subject时获取到token为:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
//查出是否有此用户
// User user=userDao.findByName(token.getUsername());
QueryWrapper<SysUser> wrapper = new QueryWrapper<SysUser>();
wrapper.eq("user_name",token.getUsername());
SysUser sysUser=sysUserMapper.selectOne(wrapper);
if(sysUser!=null){
// 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
return new SimpleAuthenticationInfo(sysUser.getUserName(), sysUser.getPassword(), getName());
}
return null;
}
}
五、新建MShiroFilterFactoryBean.java ,继承 ShiroFilterFactoryBean 处理拦截资源文件问题。
public class MShiroFilterFactoryBean extends ShiroFilterFactoryBean {
// 对ShiroFilter来说,需要直接忽略的请求
private Set<String> ignoreExt;
public MShiroFilterFactoryBean() {
super();
ignoreExt = new HashSet<String>();
ignoreExt.add(".jpg");
ignoreExt.add(".png");
ignoreExt.add(".gif");
ignoreExt.add(".bmp");
ignoreExt.add(".js");
ignoreExt.add(".css");
}
@Override
protected AbstractShiroFilter createInstance() throws Exception {
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new MSpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
private final class MSpringShiroFilter extends AbstractShiroFilter {
protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
super();
if (webSecurityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}
setSecurityManager(webSecurityManager);
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
@Override
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
String str = request.getRequestURI().toLowerCase();
// 因为ShiroFilter 拦截所有请求(在上面我们配置了urlPattern 为 * ,当然你也可以在那里精确的添加要处理的路径,这样就不需要这个类了),而在每次请求里面都做了session的读取和更新访问时间等操作,这样在集群部署session共享的情况下,数量级的加大了处理量负载。
// 所以我们这里将一些能忽略的请求忽略掉。
// 当然如果你的集群系统使用了动静分离处理,静态资料的请求不会到Filter这个层面,便可以忽略。
boolean flag = true;
int idx = 0;
if(( idx = str.indexOf(".")) > 0){
str = str.substring(idx);
if(ignoreExt.contains(str.toLowerCase()))
flag = false;
}
if(flag){
super.doFilterInternal(servletRequest, servletResponse, chain);
}else{
chain.doFilter(servletRequest, servletResponse);
}
}
}
}
六、新建ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
七、写登录退出方法
@RequestMapping(value="logining")
@ResponseBody
public JSONObject login(HttpServletRequest request, Model model, HttpServletResponse response, @Valid SysUser user, BindingResult bindingResult, RedirectAttributes redirectAttributes){
JSONObject json=new JSONObject();
model.addAttribute("kry","ssss");
if(bindingResult.hasErrors()){
json.put("flag","faliure");
return json;
}
String userName = user.getUserName();
//此处表单提交一个是否rememberMe单选框,根据值request.getParameter("rememberMe")来判断是否添加“记住”功能
//UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPwd(),true);
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),Md5Utils.md5(user.getPassword()));
//获取当前的Subject
Subject currentUser = SecurityUtils.getSubject();
try {
//在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
//每个Realm都能在必要时对提交的AuthenticationTokens作出反应
//所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
logger.info("对用户[" + userName + "]进行登录验证..验证开始");
currentUser.login(token);
Session session = currentUser.getSession(true);
//主机IP
System.out.println("host:"+session.getHost());
//session超时时间
session.setTimeout(1800000);//30分钟
// session.setTimeout(5000);//
System.out.println("timeout:"+session.getTimeout());
logger.info("对用户[" + userName + "]进行登录验证..验证通过");
}catch(UnknownAccountException uae){
logger.info("对用户[" + userName + "]进行登录验证..验证未通过,未知账户");
redirectAttributes.addFlashAttribute("message", "未知账户");
json.put("message","未知账户");
}catch(IncorrectCredentialsException ice){
logger.info("对用户[" + userName + "]进行登录验证..验证未通过,错误的凭证");
redirectAttributes.addFlashAttribute("message", "密码不正确");
json.put("message","密码不正确");
}catch(LockedAccountException lae){
logger.info("对用户[" + userName + "]进行登录验证..验证未通过,账户已锁定");
redirectAttributes.addFlashAttribute("message", "账户已锁定");
json.put("message","账户已锁定");
}catch(ExcessiveAttemptsException eae){
logger.info("对用户[" + userName + "]进行登录验证..验证未通过,错误次数过多");
redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数过多");
json.put("message","用户名或密码错误次数过多");
}catch(AuthenticationException ae){
//通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
logger.info("对用户[" + userName + "]进行登录验证..验证未通过,堆栈轨迹如下");
ae.printStackTrace();
redirectAttributes.addFlashAttribute("message", "用户名或密码不正确");
json.put("message","用户名或密码不正确");
}
//验证是否登录成功
if(currentUser.isAuthenticated()){
logger.info("用户[" + userName + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
if (currentUser.hasRole("admin")) {
logger.info("-----------admin---------");
}
if (currentUser.hasRole("user")) {
logger.info("-----------user---------");
}
json.put("flag","success");
return json;
}else{
token.clear();
json.put("flag","failure");
return json;
}
}
@RequestMapping(value="/logout",method=RequestMethod.GET)
public String logout(RedirectAttributes redirectAttributes ){
//使用权限管理工具进行用户的退出,跳出登录,给出提示信息
SecurityUtils.getSubject().logout();
redirectAttributes.addFlashAttribute("message", "您已安全退出");
logger.info("------您已安全退出-------");
return "redirect:/sys/login";
}
@RequestMapping("/403")
public String unauthorizedRole(){
logger.info("------没有权限-------");
return "sys/403";
}
到这里就可以用了(在配置过程中遇到许多问题)