一,JAVASE 环境
1.依赖引入 Pom.xml
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
- Ini配置文件 shiro.ini
[users]
zhang=123
wang=123
3.测试代码用例
@Test
public void testHelloworld() {
//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory<org.apache.shiro.mgt.SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
//2、得到SecurityManager实例并绑定给SecurityUtils
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
//4、登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
//5、身份验证失败
}
Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录
//6、退出
subject.logout();
二,WEB环境 (mvc模式)
1.依赖引入
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.2</version>
</dependency>
Web.xml配置
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro.ini</param-value>
</context-param>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ApplicationContext.xml
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!—忽略其他,详见与Spring 集成部分-->
</bean>
三,WEB环境 (springboot注解模式)
1.依赖引入 Pom.xml
<!-- Shiro 核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- Shiro-redis插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
2.注解类配置文件
/**
* Shiro配置类
*/
@Configuration
@Data
public class ShiroConfig {
private final String CACHE_KEY = "shiro:cache:";
private final String SESSION_KEY = "shiro:session:";
private Integer EXPIRE = 86400 * 7;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private Integer port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private Integer timeout;
@Order()
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 过滤器链定义映射
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/*
* anon:所有url都都可以匿名访问,authc:所有url都必须认证通过才可以访问;
* 过滤链定义,从上向下顺序执行,authc 应放在 anon 下面
* */
filterChainDefinitionMap.put("/common/**", "anon");
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
// 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/html/**", "anon");
//swagger接口权限 开放
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/doc.html", "anon");
// 所有url都必须认证通过才可以访问
filterChainDefinitionMap.put("/**", "authc");
// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面
filterChainDefinitionMap.put("/login/logout", "logout");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
// 配器shirot认登录累面地址,前后端分离中登录累面跳转应由前端路由控制,后台仅返回json数据, 对应LoginController中unauth请求
shiroFilterFactoryBean.setLoginUrl("/login/un_auth");
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
filters.put("authc", new ShiroLoginFilter());
// 退出过滤器
filters.put("logout", logoutFilter());
// shiroFilterFactoryBean.setFilters(filters);
// 登录成功后要跳转的链接, 此项目是前后端分离,故此行注释掉,登录成功之后返回用户基本信息及token给前端
// shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面, 对应LoginController中 unauthorized 请求
shiroFilterFactoryBean.setUnauthorizedUrl("/login/unauthorized");
// // 自动跳去登录的地址
// shiroFilterFactoryBean.setLoginUrl("/login");
// // 未授权页面
// shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
* 下面调用了自定义的验证类 这个方法就没有了
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//散列算法:这里使用MD5算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数,比如散列两次,相当于md5(md5(""))
hashedCredentialsMatcher.setHashIterations(1);
return hashedCredentialsMatcher;
}
/**
* 将自己的验证方式加入容器
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
return myShiroRealm;
}
/**
* RedisSessionDAOI shiro sessionDao层的实现 通过redis,使用的是shiro-redis开源插件
*
* @return
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(EXPIRE);
return redisSessionDAO;
}
/**
* Session ID生成器
*
* @return
*/
@Bean
public JavaUuidSessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
/**
* 自定义的sessionManager
*
* @return
*/
@Bean
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionDAO(redisSessionDAO());
mySessionManager.setGlobalSessionTimeout(86400000L);
return mySessionManager;
}
/**
* 配置shiro RedisManager,使用的是shiro-redis开源插件
*
* @return
*/
private RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
return redisManager;
}
/**
* 缓存redis实现,使用的shiro-redis开源查看
*
* @return
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
// 配置缓存的话要求放在session里面的实体类必须有个id标识
redisCacheManager.setPrincipalIdFieldName("id");
return redisCacheManager;
}
/**
* 安全管理器,授权管理,配置主要是Realm的管理认证
*
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义session管理 使用redis,将自定义的会话管理器注册到安全管理器中
securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis,将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(cacheManager());
// 自定义Realm验证
securityManager.setRealm(myShiroRealm());
// 记住我
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 记住我
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(cookie());
cookieRememberMeManager.setCipherKey(Base64.decode("fCq+/xW488hMTCD+cmJ3aQ=="));
return cookieRememberMeManager;
}
/**
* 退出过滤器
*
* @return
*/
public ShiroLogoutFilter logoutFilter() {
ShiroLogoutFilter logoutFilter = new ShiroLogoutFilter();
return logoutFilter;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public SimpleCookie cookie() {
//cookie的name,对应的默认是JSESSIONID
SimpleCookie cookie = new SimpleCookie("SHARE_JSESSIONID");
cookie.setHttpOnly(true);
//path 为/ 用于多个系统共享JSESSIONID
cookie.setPath("/");
return cookie;
}
}
3. 自定义认证 授权Realm
@Slf4j
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private ILoginService loginService;
@Autowired
private SysLoginService sysLoginService;
/**
* 授权权限
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.warn("开始执行授权操作.......");
User user = (User) principalCollection.getPrimaryPrincipal();
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Integer userId = user.getId();
if (userId.equals(1)) {
//管理员拥有所有权限
simpleAuthorizationInfo.addStringPermission("*:*");
} else {
List<Menu> menuList = loginService.getAuthRule();
if (!menuList.isEmpty()) {
for (Menu menu : menuList) {
if (StringUtils.isEmpty(menu.getPermission())) {
continue;
}
simpleAuthorizationInfo.addStringPermission(menu.getPermission());
}
}
}
return simpleAuthorizationInfo;
}
/**
* 认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
String password = "";
if (token.getPassword() != null) {
password = new String(token.getPassword());
}
User user = null;
try {
user = sysLoginService.login(username, password);
} catch (CaptchaException e) {
throw new AuthenticationException(e.getMessage(), e);
} catch (UserNotExistsException e) {
throw new UnknownAccountException(e.getMessage(), e);
} catch (LockedAccountException e) {
throw new LockedAccountException(e.getMessage(), e);
} catch (Exception e) {
log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
throw new AuthenticationException(e.getMessage(), e);
}
//进行验证
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用户名
user.getPassword(), //密码
ByteSource.Util.bytes(""), //设置盐值
getName()
);
return authenticationInfo;
}
}
4. 设置密码匹配验证器
/**
* 自定义验证类
*/
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken utoken = (UsernamePasswordToken) token;
String password = String.valueOf(utoken.getPassword());
String md51 = CommonUtils.md5(password.getBytes());
Object pwd = CommonUtils.md5((md51 + "IgtUdEQJyVevaCxQnY").getBytes());
Object accountCredentials = getCredentials(info);
return equals(pwd, accountCredentials);
}
}
5. 设置会话管理器
6. /**
* 自定义session管理
* 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),
* 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。
* 自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
*/
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果有请求头中有Authorization则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
oK 大功告成。
相关链接:https://www.iteye.com/blogs/subjects/shiro
Shiro 身份验证_w3cschool
Shiro的基本使用 - 随风行云 - 博客园