阅读本文大概需要 12 分钟。
来自:www.cnblogs.com/learnhow/p/5694876.html
一、架构
要学习如何使用Shiro必须先从它的架构谈起,作为一款安全框架Shiro的设计相当精妙。Shiro的应用不依赖任何容器,它也可以在JavaSE下使用。但是最常用的环境还是JavaEE。下面以用户登录为例:
1、使用用户的登录信息创建令牌
token可以理解为用户令牌,登录的过程被抽象为Shiro验证令牌是否具有合法身份以及相关权限。
2、执行登陆动作
Subject subject = SecurityUtils.getSubject(); // 获取Subject单例对象
subject.login(token); // 登陆
<?xml version=”1.0″ encoding=”UTF-8″?>
<ehcache name=“shirocache”>
<diskStore path=“java.io.tmpdir” />
<cache name=“passwordRetryCache”
maxEntriesLocalHeap=“2000”
eternal=“false”
timeToIdleSeconds=“1800”
timeToLiveSeconds=“0”
overflowToDisk=“false”
statistics=“true”>
</cache>
</ehcache>
public class PasswordHelper {
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
private String algorithmName = “md5”;
private final int hashIterations = 2;
public void encryptPassword(User user) {
// User对象包含最基本的字段Username和Password
user.setSalt(randomNumberGenerator.nextBytes().toHex());
// 将用户的注册密码经过散列算法替换成一个不可逆的新密码保存进数据,散列过程使用了盐
String newPassword = new SimpleHash(algorithmName, user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex();
user.setPassword(newPassword);
}
}
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
// 声明一个缓存接口,这个接口是Shiro缓存管理的一部分,它的具体实现可以通过外部容器注入
private Cache<String, AtomicInteger> passwordRetryCache;
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
passwordRetryCache = cacheManager.getCache(“passwordRetryCache”);
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String) token.getPrincipal();
AtomicInteger retryCount = passwordRetryCache.get(username);
if (retryCount == null) {
retryCount = new AtomicInteger(0);
passwordRetryCache.put(username, retryCount);
}
// 自定义一个验证过程:当用户连续输入密码错误5次以上禁止用户登录一段时间
if (retryCount.incrementAndGet() > 5) {
throw new ExcessiveAttemptsException();
}
boolean match = super.doCredentialsMatch(token, info);
if (match) {
passwordRetryCache.remove(username);
}
return match;
}
}
public class UserRealm extends AuthorizingRealm {
// 用户对应的角色信息与权限信息都保存在数据库中,通过UserService获取数据
private UserService userService = new UserServiceImpl();
/**
* 提供用户信息返回权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 根据用户名查询当前用户拥有的角色
Set<Role> roles = userService.findRoles(username);
Set<String> roleNames = new HashSet<String>();
for (Role role : roles) {
roleNames.add(role.getRole());
}
// 将角色名称提供给info
authorizationInfo.setRoles(roleNames);
// 根据用户名查询当前用户权限
Set<Permission> permissions = userService.findPermissions(username);
Set<String> permissionNames = new HashSet<String>();
for (Permission permission : permissions) {
permissionNames.add(permission.getPermission());
}
// 将权限名称提供给info
authorizationInfo.setStringPermissions(permissionNames);
return authorizationInfo;
}
/**
* 提供账户信息返回认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
User user = userService.findByUsername(username);
if (user == null) {
// 用户名不存在抛出异常
throw new UnknownAccountException();
}
if (user.getLocked() == 0) {
// 用户被管理员锁定抛出异常
throw new LockedAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),
user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
return authenticationInfo;
}
}
-
用户表(存储用户名,密码,盐等)
-
角色表(角色名称,相关描述等)
-
权限表(权限名称,相关描述等)
-
用户-角色对应中间表(以用户ID和角色ID作为联合主键)
-
角色-权限对应中间表(以角色ID和权限ID作为联合主键)
<?xml version=”1.0″ encoding=”UTF-8″?>
<web-app xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xmlns=“http://java.sun.com/xml/ns/javaee”
xsi:schemaLocation=“http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd”
id=“WebApp_ID” version=“3.0”>
<display-name>Shiro_Project</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<!– 将Shiro的配置文件交给Spring监听器初始化 –>
<param-value>classpath:spring.xml,classpath:spring-shiro-web.xml</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLoaction</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<!– shiro配置 开始 –>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<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>
<!– shiro配置 结束 –>
</web-app>
<beans xmlns=“http://www.springframework.org/schema/beans”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:p=“http://www.springframework.org/schema/p”
xmlns:context=“http://www.springframework.org/schema/context”
xmlns:mvc=“http://www.springframework.org/schema/mvc”
xsi:schemaLocation=“http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd”>
<!– 缓存管理器 使用Ehcache实现 –>
<bean id=“cacheManager” class=“org.apache.shiro.cache.ehcache.EhCacheManager”>
<property name=“cacheManagerConfigFile” value=“classpath:ehcache.xml” />
</bean>
<!– 凭证匹配器 –>
<bean id=“credentialsMatcher” class=“utils.RetryLimitHashedCredentialsMatcher”>
<constructor-arg ref=“cacheManager” />
<property name=“hashAlgorithmName” value=“md5” />
<property name=“hashIterations” value=“2” />
<property name=“storedCredentialsHexEncoded” value=“true” />
</bean>
<!– Realm实现 –>
<bean id=“userRealm” class=“utils.UserRealm”>
<property name=“credentialsMatcher” ref=“credentialsMatcher” />
</bean>
<!– 安全管理器 –>
<bean id=“securityManager” class=“org.apache.shiro.web.mgt.DefaultWebSecurityManager”>
<property name=“realm” ref=“userRealm” />
</bean>
<!– Shiro的Web过滤器 –>
<bean id=“shiroFilter” class=“org.apache.shiro.spring.web.ShiroFilterFactoryBean”>
<property name=“securityManager” ref=“securityManager” />
<property name=“loginUrl” value=“/” />
<property name=“unauthorizedUrl” value=“/” />
<property name=“filterChainDefinitions”>
<value>
/authc/admin = roles[admin]
/authc/** = authc
/** = anon
</value>
</property>
</bean>
<bean id=“lifecycleBeanPostProcessor” class=“org.apache.shiro.spring.LifecycleBeanPostProcessor” />
</beans>
-
/authc/admin需要用户有用admin权限
-
/authc/**用户必须登录才能访问
-
/**其他所有路径任何人都可以访问
@Controller
public class LoginController {
@Autowired
private UserService userService;
@RequestMapping(“login”)
public ModelAndView login(@RequestParam(“username”) String username, @RequestParam(“password”) String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
// 捕获密码错误异常
ModelAndView mv = new ModelAndView(“error”);
mv.addObject(“message”, “password error!”);
return mv;
} catch (UnknownAccountException uae) {
// 捕获未知用户名异常
ModelAndView mv = new ModelAndView(“error”);
mv.addObject(“message”, “username error!”);
return mv;
} catch (ExcessiveAttemptsException eae) {
// 捕获错误登录过多的异常
ModelAndView mv = new ModelAndView(“error”);
mv.addObject(“message”, “times error”);
return mv;
}
User user = userService.findByUsername(username);
subject.getSession().setAttribute(“user”, user);
return new ModelAndView(“success”);
}
}
@Controller
@RequestMapping(“authc”)
public class AuthcController {
// /authc/** = authc 任何通过表单登录的用户都可以访问
@RequestMapping(“anyuser”)
public ModelAndView anyuser() {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession().getAttribute(“user”);
System.out.println(user);
return new ModelAndView(“inner”);
}
// /authc/admin = user[admin] 只有具备admin角色的用户才可以访问,否则请求将被重定向至登录界面
@RequestMapping(“admin”)
public ModelAndView admin() {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession().getAttribute(“user”);
System.out.println(user);
return new ModelAndView(“inner”);
}
}
推荐阅读:
亲身经历:一个00后的中专生是怎么在深圳拿到15K(Java后端开发)
微信扫描二维码,关注我的公众号
朕已阅
文章收集整理于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除,如若转载,请注明出处:http://www.cxyroad.com/1669.html