Shiro核心对象
-
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。
Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权 -
SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。 -
Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。 -
Authorizer Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
-
realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。 -
sessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
Shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
Shiro配置
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
//当拦截到请求时,去AjaxPermissionsAuthorizationFilter()处理
//去AjaxPermissionsAuthorizationFilter是自定义的类,继承与FormAuthenticationFilter
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/*定义shiro过滤链 Map结构
* Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的
* anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种
* authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/* 过滤链定义,从上向下顺序执行,一般将 / ** 放在最为下边:这是一个坑呢,一不小心代码就不好使了;
authc:所有url都必须认证通过才可以访问; anon:所有url都可以匿名访问 */
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login/auth", "anon");
filterChainDefinitionMap.put("/login/getCode", "anon");
filterChainDefinitionMap.put("/login/logout", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/doc.html", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//配置securityManager
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//这里要设置Realm
securityManager.setRealm(userRealm());
return securityManager;
}
//配置userRealm,如果要使用登录加密则配置setCredentialsMatcher
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
//配置加密规则
@Bean()
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
//storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
自定义的realm
public class UserRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(UserRealm.class);
//登录的service,就是dao接口,作用获取数据库用户信息
@Autowired
private LoginService loginService;
@Autowired
private UserService userService;
@Override
@SuppressWarnings("unchecked")
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取用户
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String primaryPrincipal = (String)principals.getPrimaryPrincipal();
System.out.println("调用权限验证:" + primaryPrincipal);
//获取用户信息
JSONObject user = loginService.getUser(primaryPrincipal);
String role = (String) user.get("role_name");
//为用户添加角色
authorizationInfo.addRole(role);
//返回authorizationInfo
return authorizationInfo;
}
/**
* 验证当前登录的Subject
* LoginController.login()方法中执行Subject.login()时 执行此方法
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
String loginName = (String) authcToken.getPrincipal();
// 获取用户密码
String password = new String((char[]) authcToken.getCredentials());
JSONObject user = loginService.getUser(loginName);
if (user == null) {
//没找到帐号
throw new UnknownAccountException();
}
user.put("roleName",loginService.getUser(loginName).get("role_name"));
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getString("login_name"),
user.getString("password"),
ByteSource.Util.bytes(user.getString("salt")),
getName()
);
//session中不需要保存密码
user.remove("password");
SecurityUtils.getSubject().getSession().setAttribute(Constants.SESSION_USER_INFO, user);
return authenticationInfo;
}
}
编写Controller的登录逻辑
public JSONObject authLogin(JSONObject jsonObject) {
String username = jsonObject.getString("loginName");
String password = jsonObject.getString("password");
String code = jsonObject.getString("code");
JSONObject data = new JSONObject();
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//判断验证码
if(code == null || code.equals("")){
return CommonUtil.failJson(data);
}
String relcode = (String)currentUser.getSession().getAttribute("code");
if(relcode == null || relcode.equals("")){
data.put("code", "201");
data.put("msg", "验证码失效");
return data;
}
if(!relcode.equals(relcode)){
data.put("code", "201");
data.put("msg", "验证码错误");
return data;
}
try {
//交给shiro验证,实质是调用UserRealm里逻辑,失败则会抛出异常。
currentUser.login(token);
data.put("code", "200");
data.put("msg", "请求成功");
} catch (AuthenticationException e) {
data.put("code", "201");
data.put("msg", "账号或密码错误");
}
return data;
}
注册加密逻辑
如果采用加密形式,则注册时候要将密码加密存到数据库
String originalPassword = user.getPassword(); //原始密码
String hashAlgorithmName = "md5"; //加密方式
int hashIterations = 2; //加密的次数
//随机盐
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
//加密
SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, originalPassword, salt, hashIterations);
//加密后密码
String encryptionPassword = simpleHash.toString();
//存到用户对象里
user.setSalt(salt);
user.setPassword(encryptionPassword);
//...其他逻辑
//存到数据库
userDao.addUser(user);