shiro容器思想
使用过spring容器的都知道IOC/DI的思想,经过对容器概念的理解,再对ini配置的内容就可以方便的映射到容器中SecurityManager的组件过程,并且用户通过SecurityUtils进行使用,因此可以通过工厂模式中获取ini的SecurityManager的对象,进而通过SecurityUtils.set()的方式设置该SecurityManager接口的具体实例后在就可以通过SecurityUtils工具类提供的Subject进行基本使用了。这里Subject使用了门面模式的设计思想。
基本数据库数据
drop database if exists shiro;
create database shiro;
use shiro;
create table users (
id bigint auto_increment,
username varchar(100),
password varchar(100),
password_salt varchar(100),
constraint pk_users primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_users_username on users(username);
create table user_roles(
id bigint auto_increment,
username varchar(100),
role_name varchar(100),
constraint pk_user_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_user_roles on user_roles(username, role_name);
create table roles_permissions(
id bigint auto_increment,
role_name varchar(100),
permission varchar(100),
constraint pk_roles_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_roles_permissions on roles_permissions(role_name, permission);
insert into users(username,password)values('zhang','123');
# --------------------------授权阶段,提供角色信息和其他的资源等-------------------------------
delete from users;
delete from user_roles;
delete from roles_permissions;
insert into users(username, password, password_salt) values('zhang', '123', null);
insert into user_roles(username, role_name) values('zhang', 'role1');
insert into user_roles(username, role_name) values('zhang', 'role2');
insert into roles_permissions(role_name, permission) values('role1', '+user1+10');
insert into roles_permissions(role_name, permission) values('role1', 'user1:*');
insert into roles_permissions(role_name, permission) values('role1', '+user2+10');
insert into roles_permissions(role_name, permission) values('role1', 'user2:*');
ini方式使用
[main]
\#authenticator
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
authenticator.authenticationStrategy=$authenticationStrategy
securityManager.authenticator=$authenticator
\#authorizer
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
\#realm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
\#dataSource.password=
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm;
[users]
\#提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
username=password,role1,role2
[roles]
\#提供了角色及权限之间关系的配置,角色=权限1,权限2
role1=permission1,permission2
[urls]
\#用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin], perms["permission1"]
对于其中的详细构建过程可以通过Java的方式进行构建securityManager.这里仅仅进行理解即可,因为会有许多三方框架也通过类似ini注入的方式对其进行实例化。
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.authz.permission.WildcardPermissionResolver;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import java.util.Arrays;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class JavaIniTest {
@Test
public void testiniJava(){
DefaultSecurityManager securityManager=new DefaultSecurityManager();
//---------------认证模块设置
ModularRealmAuthenticator authenticator=new ModularRealmAuthenticator();
//设置认证的策略,只要有一个realm通过,才算认证通过
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
//---------------设置授权模块
ModularRealmAuthorizer authorizer=new ModularRealmAuthorizer();
//设置默认的授权解析器(当有特殊需求的时候,我们可以自定义--就像上一篇中字节类解析其一样)
authorizer.setPermissionResolver(new WildcardPermissionResolver());
//这里可以设置角色解析器,需要用户实现RolePermissionResolver接口来进行设置
//---------------设置Realm
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql:///shiro");
ds.setUsername("root");
ds.setPassword("riversky");
JdbcRealm jdbcRealm=new JdbcRealm();
jdbcRealm.setDataSource(ds);
//資源的解析必要的
jdbcRealm.setPermissionsLookupEnabled(true);
//-------------组件SecurityManager
securityManager.setAuthenticator(authenticator);
securityManager.setAuthorizer(authorizer);
securityManager.setRealms(Arrays.asList((Realm)jdbcRealm));
//-------------对外提供使用的SeurityUtils工具类,可以获取Subject门面类
SecurityUtils.setSecurityManager(securityManager);
Subject subject=SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken("zhang","123"));
System.out.println(subject.hasRole("role1"));
System.out.println(subject.isPermitted("user:create"));
subject.logout();
}
}
加密辅助模块
关于加密一般常用的就是非对称的散列,但是如果简单的md5或者SHA类型的散列很容易让人推测出来,比如常见的admin,经过散列后是固定的值21232f297a57a5a743894a0e4a801fc3。那么当不良的用户看到数据库数据后,就可以反推出密码。因此Shiro提出了常用的加盐和多次散列的方案。一般盐会在用户注册时生成随机数后存到数据库中,而散列次数可以采取固定或者其他配置文件中读取的方式。
package cn.riversky;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.junit.Test;
/**
* Shiro提供了几种支持加盐的散列算法Md5 ,sha系列的几种方案
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class EncodeTest {
@Test
public void eccoude(){
//常规的md5
System.out.println(new Md5Hash("admin"));
//表示对admin加盐123后进行3次的md5
System.out.println(new Md5Hash("admin","123",3));
//使用sha256方案类似的加密方法
System.out.println(new Sha256Hash("admin","123",3));
}
}
Shiro中使用密码模块的话组建方式如下(例ini–当然可以采取其他的方式)
依赖关系为
结构 | |
---|---|
hashService,hashFormat,hashFormatFactory->passwordService | passwordService->passwordMatcher->myRelm,passwordService->realm |
[main]
passwordService=org.apache.shiro.authc.credential.DefaultPasswordService
hashService=org.apache.shiro.crypto.hash.DefaultHashService
passwordService.hashService=$hashService
hashFormat=org.apache.shiro.crypto.hash.format.Shiro1CryptFormat
passwordService.hashFormat=$hashFormat
hashFormatFactory=org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory
passwordService.hashFormatFactory=$hashFormatFactory
passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher
passwordMatcher.passwordService=$passwordService
myRealm=com.github.zhangkaitao.shiro.chapter5.hash.realm.MyRealm
myRealm.passwordService=$passwordService
myRealm.credentialsMatcher=$passwordMatcher
securityManager.realms=$myRealm
当然在登录时也可以进行优化策略,比如防止恶意登录验证,可以使用ehcache缓存或者redis缓存实现密码错误计数,当次数达到一定数量就直接抛出excessiveAttemptsException()异常
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">
<diskStore path="java.io.tmpdir"/>
<!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
增强的CredentialsMatcher
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 如在 1 个小时内密码最多重试 5 次,如果尝试次数超过 5 次就锁定 1 小时,1 小时后可再次重试,
* 如果还是重试失败,可以锁定如 1 天,以此类推,防止密码被暴力破解,使用Ehcache 记录重试次数和超时时间。
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher{
private Ehcache passwordRetryCache;
public RetryLimitHashedCredentialsMatcher() {
CacheManager cacheManager=CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml"));
passwordRetryCache=cacheManager.getCache("passwordRetryCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username=(String) token.getPrincipal();
Element element=passwordRetryCache.get(username);
if(element==null){
element=new Element(username,new AtomicInteger(0));
passwordRetryCache.put(element);
}
AtomicInteger retryCount= (AtomicInteger) element.getObjectValue();
if(retryCount.incrementAndGet()>5){
throw new ExcessiveAttemptsException();
}
boolean matches=super.doCredentialsMatch(token,info);
if(matches){
passwordRetryCache.remove(username);
}
return matches;
}
基本使用模式–非web环境
整个设计的类关系如图
对于各个层次和源码如下
entity层,定义基本的bean
package cn.riversky.entity;
import java.io.Serializable;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class Permission implements Serializable{
private Long id;
private String permission; //权限标识 程序中判断使用,如"user:create"
private String description; //权限描述,UI界面显示使用
private Boolean available = Boolean.FALSE; //是否可用,如果不可用将不会添加给用户
public Permission() {
}
public Permission(String permission, String description, Boolean available) {
this.permission = permission;
this.description = description;
this.available = available;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Permission role = (Permission) o;
if (id != null ? !id.equals(role.id) : role.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", permission='" + permission + '\'' +
", description='" + description + '\'' +
", available=" + available +
'}';
}
}
package cn.riversky.entity;
import java.io.Serializable;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class Role implements Serializable {
private Long id;
private String role; //角色标识 程序中判断使用,如"admin"
private String description; //角色描述,UI界面显示使用
private Boolean available = Boolean.FALSE; //是否可用,如果不可用将不会添加给用户
public Role() {
}
public Role(String role, String description, Boolean available) {
this.role = role;
this.description = description;
this.available = available;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Role role = (Role) o;
if (id != null ? !id.equals(role.id) : role.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", role='" + role + '\'' +
", description='" + description + '\'' +
", available=" + available +
'}';
}
}
package cn.riversky.entity;
import java.io.Serializable;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class RolePermssion implements Serializable{
private Long roleId;
private Long permissionId;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getPermissionId() {
return permissionId;
}
public void setPermissionId(Long permissionId) {
this.permissionId = permissionId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RolePermssion that = (RolePermssion) o;
if (permissionId != null ? !permissionId.equals(that.permissionId) : that.permissionId != null) return false;
if (roleId != null ? !roleId.equals(that.roleId) : that.roleId != null) return false;
return true;
}
@Override
public int hashCode() {
int result = roleId != null ? roleId.hashCode() : 0;
result = 31 * result + (permissionId != null ? permissionId.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "RolePermssion{" +
"roleId=" + roleId +
", permissionId=" + permissionId +
'}';
}
}
package cn.riversky.entity;
import java.io.Serializable;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class User implements Serializable{
private Long id;
private String username;
private String password;
private String salt;
private Boolean locked = Boolean.FALSE;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getCredentialsSalt() {
return username + salt;
}
public Boolean getLocked() {
return locked;
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != null ? !id.equals(user.id) : user.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
", locked=" + locked +
'}';
}
}
package cn.riversky.entity;
import java.io.Serializable;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class UserRole implements Serializable {
private Long userId;
private Long roleId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRole userRole = (UserRole) o;
if (roleId != null ? !roleId.equals(userRole.roleId) : userRole.roleId != null) return false;
if (userId != null ? !userId.equals(userRole.userId) : userRole.userId != null) return false;
return true;
}
@Override
public int hashCode() {
int result = userId != null ? userId.hashCode() : 0;
result = 31 * result + (roleId != null ? roleId.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "UserRole{" +
"userId=" + userId +
", roleId=" + roleId +
'}';
}
}
dao层设计/借助spring-jdbc实现连接
package cn.riversky.utils;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class JdbcTemplateUtils {
private static JdbcTemplate jdbcTemplate;
/**
* 单例模式懒汉室
* @return
*/
public static JdbcTemplate jdbcTemplate(){
if(jdbcTemplate==null){
jdbcTemplate=createJdbcTemplate();
}
return jdbcTemplate;
}
private static JdbcTemplate createJdbcTemplate(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/shiro");
ds.setUsername("root");
ds.setPassword("riversky");
return new JdbcTemplate(ds);
}
}
package cn.riversky.dao;
import cn.riversky.entity.Permission;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public interface PermissionDao {
Permission createPermission(Permission permission);
void deletePermission(Long permissionId);
}
package cn.riversky.dao;
import cn.riversky.entity.Permission;
import cn.riversky.utils.JdbcTemplateUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class PermissionDaoImpl implements PermissionDao {
private JdbcTemplate jdbcTemplate= JdbcTemplateUtils.jdbcTemplate();
@Override
public Permission createPermission(final Permission permission) {
final String sql="insert into sys_permissions(permission, description, available) values(?,?,?)";
GeneratedKeyHolder keyHolder=new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement pstm=connection.prepareStatement(sql,new String[]{"id"});
pstm.setString(1,permission.getPermission());
pstm.setString(2,permission.getDescription());
pstm.setBoolean(3,permission.getAvailable());
return pstm;
}
},keyHolder);
permission.setId(keyHolder.getKey().longValue());
return permission;
}
@Override
public void deletePermission(Long permissionId) {
//首先把与permission关联的相关表的数据删掉
String sql = "delete from sys_roles_permissions where permission_id=?";
jdbcTemplate.update(sql, permissionId);
sql = "delete from sys_permissions where id=?";
jdbcTemplate.update(sql, permissionId);
}
}
package cn.riversky.dao;
import cn.riversky.entity.Role;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public interface RoleDao {
Role createRole(Role role);
void deleteRole(Long roleId);
void correlationPermissions(Long roleId,Long... permissionIds);
void uncorrelationPermissions(Long roleId,Long... permissionIds);
}
package cn.riversky.dao;
import cn.riversky.entity.Role;
import cn.riversky.utils.JdbcTemplateUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class RoleDaoImpl implements RoleDao {
private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();
@Override
public Role createRole(final Role role) {
final String sql = "insert into sys_roles(role, description, available) values(?,?,?)";
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement psst = connection.prepareStatement(sql, new String[] { "id" });
psst.setString(1, role.getRole());
psst.setString(2, role.getDescription());
psst.setBoolean(3, role.getAvailable());
return psst;
}
}, keyHolder);
role.setId(keyHolder.getKey().longValue());
return role;
}
@Override
public void deleteRole(Long roleId) {
//首先把和role关联的相关表数据删掉
String sql = "delete from sys_users_roles where role_id=?";
jdbcTemplate.update(sql, roleId);
sql = "delete from sys_roles where id=?";
jdbcTemplate.update(sql, roleId);
}
@Override
public void correlationPermissions(Long roleId, Long... permissionIds) {
if(permissionIds == null || permissionIds.length == 0) {
return;
}
String sql = "insert into sys_roles_permissions(role_id, permission_id) values(?,?)";
for(Long permissionId : permissionIds) {
if(!exists(roleId, permissionId)) {
jdbcTemplate.update(sql, roleId, permissionId);
}
}
}
@Override
public void uncorrelationPermissions(Long roleId, Long... permissionIds) {
if(permissionIds == null || permissionIds.length == 0) {
return;
}
String sql = "delete from sys_roles_permissions where role_id=? and permission_id=?";
for(Long permissionId : permissionIds) {
if(exists(roleId, permissionId)) {
jdbcTemplate.update(sql, roleId, permissionId);
}
}
}
private boolean exists(Long roleId, Long permissionId) {
String sql = "select count(1) from sys_roles_permissions where role_id=? and permission_id=?";
return jdbcTemplate.queryForObject(sql, Integer.class, roleId, permissionId) != 0;
}
}
package cn.riversky.dao;
import cn.riversky.entity.User;
import java.util.Set;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public interface UserDao {
User createUser(User user);
void updateUser(User user);
void deleteUser(Long userId);
void correlationRoles(Long userId,Long... roleIds);
void uncorrelationRoles(Long userId,Long... roleids);
User findOne(Long userId);
User findByUsername(String username);
Set<String> findRoles(String username);
Set<String> findPermissions(String username);
}
package cn.riversky.dao;
import cn.riversky.entity.User;
import cn.riversky.utils.JdbcTemplateUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class UserDaoImpl implements UserDao{
private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();
@Override
public User createUser(final User user) {
final String sql = "insert into sys_users(username, password, salt, locked) values(?,?,?, ?)";
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement psst = connection.prepareStatement(sql, new String[] { "id" });
psst.setString(1, user.getUsername());
psst.setString(2, user.getPassword());
psst.setString(3, user.getSalt());
psst.setBoolean(4, user.getLocked());
return psst;
}
}, keyHolder);
user.setId(keyHolder.getKey().longValue());
return user;
}
@Override
public void updateUser(User user) {
String sql = "update sys_users set username=?, password=?, salt=?, locked=? where id=?";
jdbcTemplate.update(sql, user.getUsername(), user.getPassword(), user.getSalt(), user.getLocked(), user.getId());
}
@Override
public void deleteUser(Long userId) {
String sql = "delete from sys_users where id=?";
jdbcTemplate.update(sql, userId);
}
@Override
public void correlationRoles(Long userId, Long... roleIds) {
if(roleIds == null || roleIds.length == 0) {
return;
}
String sql = "insert into sys_users_roles(user_id, role_id) values(?,?)";
for(Long roleId : roleIds) {
if(!exists(userId, roleId)) {
jdbcTemplate.update(sql, userId, roleId);
}
}
}
@Override
public void uncorrelationRoles(Long userId, Long... roleIds) {
if(roleIds == null || roleIds.length == 0) {
return;
}
String sql = "delete from sys_users_roles where user_id=? and role_id=?";
for(Long roleId : roleIds) {
if(exists(userId, roleId)) {
jdbcTemplate.update(sql, userId, roleId);
}
}
}
private boolean exists(Long userId, Long roleId) {
String sql = "select count(1) from sys_users_roles where user_id=? and role_id=?";
return jdbcTemplate.queryForObject(sql, Integer.class, userId, roleId) != 0;
}
@Override
public User findOne(Long userId) {
String sql = "select id, username, password, salt, locked from sys_users where id=?";
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class), userId);
if(userList.size() == 0) {
return null;
}
return userList.get(0);
}
@Override
public User findByUsername(String username) {
String sql = "select id, username, password, salt, locked from sys_users where username=?";
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class), username);
if(userList.size() == 0) {
return null;
}
return userList.get(0);
}
@Override
public Set<String> findRoles(String username) {
String sql = "select role from sys_users u, sys_roles r,sys_users_roles ur where u.username=? and u.id=ur.user_id and r.id=ur.role_id";
return new HashSet(jdbcTemplate.queryForList(sql, String.class, username));
}
@Override
public Set<String> findPermissions(String username) {
//TODO 此处可以优化,比如查询到role后,一起获取roleId,然后直接根据roleId获取即可
String sql = "select permission from sys_users u, sys_roles r, sys_permissions p, sys_users_roles ur, sys_roles_permissions rp where u.username=? and u.id=ur.user_id and r.id=ur.role_id and r.id=rp.role_id and p.id=rp.permission_id";
return new HashSet(jdbcTemplate.queryForList(sql, String.class, username));
}
}
service层设计
需要注意的是为了屏蔽密码设计细节,因此通过passwordHelper类实现了,md5,和两次散列以及自动随机数生成盐的方式。这里需要与credentialsMatcher–RetryLimitHashedCredentialsMatcher使用自定义的中的验证过程一致,不然会出现问题。
package cn.riversky.service;
import cn.riversky.entity.Permission;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public interface PermissionService {
public Permission createPermission(Permission permission);
public void deletePermission(Long permissionId);
}
package cn.riversky.service;
import cn.riversky.dao.PermissionDao;
import cn.riversky.dao.PermissionDaoImpl;
import cn.riversky.entity.Permission;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class PermissionServiceImpl implements PermissionService {
private PermissionDao permissionDao = new PermissionDaoImpl();
@Override
public Permission createPermission(Permission permission) {
return permissionDao.createPermission(permission);
}
@Override
public void deletePermission(Long permissionId) {
permissionDao.deletePermission(permissionId);
}
}
package cn.riversky.service;
import cn.riversky.entity.Role;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public interface RoleService {
public Role createRole(Role role);
public void deleteRole(Long roleId);
/**
* 添加角色-权限之间关系
* @param roleId
* @param permissionIds
*/
public void correlationPermissions(Long roleId, Long... permissionIds);
/**
* 移除角色-权限之间关系
* @param roleId
* @param permissionIds
*/
public void uncorrelationPermissions(Long roleId, Long... permissionIds);
}
package cn.riversky.service;
import cn.riversky.dao.RoleDao;
import cn.riversky.dao.RoleDaoImpl;
import cn.riversky.entity.Role;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class RoleServiceImpl implements RoleService {
private RoleDao roleDao=new RoleDaoImpl();
@Override
public Role createRole(Role role) {
return roleDao.createRole(role);
}
@Override
public void deleteRole(Long roleId) {
roleDao.deleteRole(roleId);
}
/**
* 添加角色-权限之间关系
* @param roleId
* @param permissionIds
*/
@Override
public void correlationPermissions(Long roleId, Long... permissionIds) {
roleDao.correlationPermissions(roleId, permissionIds);
}
/**
* 移除角色-权限之间关系
* @param roleId
* @param permissionIds
*/
@Override
public void uncorrelationPermissions(Long roleId, Long... permissionIds) {
roleDao.uncorrelationPermissions(roleId, permissionIds);
}
}
package cn.riversky.service;
import cn.riversky.entity.User;
import java.util.Set;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public interface UserService {
/**
* 创建用户
* @param user
*/
public User createUser(User user);
/**
* 修改密码
* @param userId
* @param newPassword
*/
public void changePassword(Long userId, String newPassword);
/**
* 添加用户-角色关系
* @param userId
* @param roleIds
*/
public void correlationRoles(Long userId, Long... roleIds);
/**
* 移除用户-角色关系
* @param userId
* @param roleIds
*/
public void uncorrelationRoles(Long userId, Long... roleIds);
/**
* 根据用户名查找用户
* @param username
* @return
*/
public User findByUsername(String username);
/**
* 根据用户名查找其角色
* @param username
* @return
*/
public Set<String> findRoles(String username);
/**
* 根据用户名查找其权限
* @param username
* @return
*/
public Set<String> findPermissions(String username);
}
package cn.riversky.service;
import cn.riversky.dao.UserDao;
import cn.riversky.dao.UserDaoImpl;
import cn.riversky.entity.User;
import java.util.Set;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class UserServiceImpl implements UserService{
private UserDao userDao=new UserDaoImpl();
private PasswordHelper passwordHelper=new PasswordHelper();
/**
* 创建用户
* @param user
*/
@Override
public User createUser(User user) {
//加密密码
passwordHelper.encryptPassword(user);
return userDao.createUser(user);
}
/**
* 修改密码
* @param userId
* @param newPassword
*/
@Override
public void changePassword(Long userId, String newPassword) {
User user =userDao.findOne(userId);
user.setPassword(newPassword);
passwordHelper.encryptPassword(user);
userDao.updateUser(user);
}
/**
* 添加用户-角色关系
* @param userId
* @param roleIds
*/
@Override
public void correlationRoles(Long userId, Long... roleIds) {
userDao.correlationRoles(userId, roleIds);
}
/**
* 移除用户-角色关系
* @param userId
* @param roleIds
*/
@Override
public void uncorrelationRoles(Long userId, Long... roleIds) {
userDao.uncorrelationRoles(userId, roleIds);
}
/**
* 根据用户名查找用户
* @param username
* @return
*/
@Override
public User findByUsername(String username) {
return userDao.findByUsername(username);
}
/**
* 根据用户名查找其角色
* @param username
* @return
*/
@Override
public Set<String> findRoles(String username) {
return userDao.findRoles(username);
}
/**
* 根据用户名查找其权限
* @param username
* @return
*/
@Override
public Set<String> findPermissions(String username) {
return userDao.findPermissions(username);
}
}
package cn.riversky.service;
import cn.riversky.entity.User;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class PasswordHelper {
private RandomNumberGenerator randomNumberGenerator=new SecureRandomNumberGenerator();
private String algorithmName="md5";
private final int hashIterations=2;
public void encryptPassword(User user){
user.setSalt(randomNumberGenerator.nextBytes().toHex());
String newPassword=new SimpleHash(algorithmName,user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()),hashIterations).toHex();
user.setPassword(newPassword);
}
}
Shiro部分的credentialsMatcher和Realm
主配置文件
[main]
credentialsMatcher=cn.riversky.credentials.RetryLimitHashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
credentialsMatcher.storedCredentialsHexEncoded=true
userRealm=cn.riversky.realm.UserRealm
userRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$userRealm
package cn.riversky.realm;
import cn.riversky.entity.User;
import cn.riversky.service.UserService;
import cn.riversky.service.UserServiceImpl;
import org.apache.shiro.authc.*;
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;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class UserRealm extends AuthorizingRealm {
private UserService userService = new UserServiceImpl();
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
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(Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); //帐号锁定
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(), //用户名
user.getPassword(), //密码
ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
}
package cn.riversky.credentials;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher{
private Ehcache passwordRetryCache;
public RetryLimitHashedCredentialsMatcher() {
CacheManager cacheManager = CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml"));
passwordRetryCache = cacheManager.getCache("passwordRetryCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String)token.getPrincipal();
//retry count + 1
Element element = passwordRetryCache.get(username);
if(element == null) {
element = new Element(username , new AtomicInteger(0));
passwordRetryCache.put(element);
}
AtomicInteger retryCount = (AtomicInteger)element.getObjectValue();
if(retryCount.incrementAndGet() > 5) {
//if retry count > 5 throw
throw new ExcessiveAttemptsException();
}
boolean matches = super.doCredentialsMatch(token, info);
if(matches) {
//clear retry count
passwordRetryCache.remove(username);
}
return matches;
}
}
数据库结构和数据
drop table if exists sys_users;
drop table if exists sys_roles;
drop table if exists sys_permissions;
drop table if exists sys_users_roles;
drop table if exists sys_roles_permissions;
create table sys_users (
id bigint auto_increment,
username varchar(100),
password varchar(100),
salt varchar(100),
locked bool default false,
constraint pk_sys_users primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_sys_users_username on sys_users(username);
create table sys_roles (
id bigint auto_increment,
role varchar(100),
description varchar(100),
available bool default false,
constraint pk_sys_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_sys_roles_role on sys_roles(role);
create table sys_permissions (
id bigint auto_increment,
permission varchar(100),
description varchar(100),
available bool default false,
constraint pk_sys_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_sys_permissions_permission on sys_permissions(permission);
create table sys_users_roles (
user_id bigint,
role_id bigint,
constraint pk_sys_users_roles primary key(user_id, role_id)
) charset=utf8 ENGINE=InnoDB;
create table sys_roles_permissions (
role_id bigint,
permission_id bigint,
constraint pk_sys_roles_permissions primary key(role_id, permission_id)
) charset=utf8 ENGINE=InnoDB;
内部需要的数据我们在测试中生成和使用
package cn.riversky.demo;
import cn.riversky.entity.Permission;
import cn.riversky.entity.Role;
import cn.riversky.entity.User;
import cn.riversky.service.*;
import cn.riversky.utils.JdbcTemplateUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.util.ThreadContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class BaseTest {
protected PermissionService permissionService=new PermissionServiceImpl();
protected RoleService roleService=new RoleServiceImpl();
protected UserService userService=new UserServiceImpl();
protected String password="123";
protected Permission p1;
protected Permission p2;
protected Permission p3;
protected Role r1;
protected Role r2;
protected User u1;
protected User u2;
protected User u3;
protected User u4;
@Before
public void setUp(){
JdbcTemplateUtils.jdbcTemplate().update("delete from sys_users");
JdbcTemplateUtils.jdbcTemplate().update("delete from sys_roles");
JdbcTemplateUtils.jdbcTemplate().update("delete from sys_permissions");
JdbcTemplateUtils.jdbcTemplate().update("delete from sys_users_roles");
JdbcTemplateUtils.jdbcTemplate().update("delete from sys_roles_permissions");
//新增权限
p1=new Permission("user:create","用户模块新增",Boolean.TRUE);
p2=new Permission("user:update","用户模块修改",Boolean.TRUE);
p3=new Permission("menu:update","菜单模块修改",Boolean.TRUE);
permissionService.createPermission(p1);
permissionService.createPermission(p2);
permissionService.createPermission(p3);
//新增角色
r1=new Role("admin","管理员",Boolean.TRUE);
r2=new Role("user","用户管理",Boolean.TRUE);
roleService.createRole(r1);
roleService.createRole(r2);
//关联角色-权限
roleService.correlationPermissions(r1.getId(),p1.getId(),p2.getId(),p3.getId());
roleService.correlationPermissions(r2.getId(),p2.getId(),p1.getId());
//新增用户
u1=new User("zhang",password);
u2=new User("li",password);
u3=new User("wu",password);
u4=new User("wang",password);
u4.setLocked(true);
userService.createUser(u1);
userService.createUser(u2);
userService.createUser(u3);
userService.createUser(u4);
//关联用户与角色
userService.correlationRoles(u1.getId(),r1.getId());
}
@After
public void tearDown()throws Exception{
//退出时请解除绑定Subject到线程 否则对下次测试造成影响
ThreadContext.unbindSubject();
}
protected void login(String configFile,String username,String password){
Factory<SecurityManager> factory=new IniSecurityManagerFactory(configFile);
SecurityManager manager=factory.getInstance();
SecurityUtils.setSecurityManager(manager);
Subject subject=subject();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
subject.login(token);
}
public Subject subject(){
return SecurityUtils.getSubject();
}
@Test
public void test(){
login("classpath:shiro.ini","zhang","123");
Subject subject=SecurityUtils.getSubject();
System.out.println(subject.isPermitted("user:create"));
}
}
package cn.riversky.demo;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.junit.Assert;
import org.junit.Test;
/**
* @author riversky E-mail:[email protected]
* @version 创建时间 : 2018/1/25.
*/
public class UserRealmTest extends BaseTest {
@Test
public void testLoginSuccess(){
login("classpath:shiro.ini",u1.getUsername(),password);
System.out.println(u1);
//判断是否通过认证
System.out.println(subject().isAuthenticated());
}
@Test(expected = UnknownAccountException.class)
public void testLoginFailWithUnknownUsername() {
login("classpath:shiro.ini", u1.getUsername() + "1", password);
}
@Test(expected = IncorrectCredentialsException.class)
public void testLoginFailWithErrorPassowrd() {
login("classpath:shiro.ini", u1.getUsername(), password + "1");
}
@Test(expected = LockedAccountException.class)
public void testLoginFailWithLocked() {
login("classpath:shiro.ini", u4.getUsername(), password + "1");
}
@Test(expected = ExcessiveAttemptsException.class)
public void testLoginFailWithLimitRetryCount() {
for (int i = 0; i <= 5; i++) {
try {
login("classpath:shiro.ini",u3.getUsername(),password+"1");
}catch (Exception e){
}
}
login("classpath:shiro.ini",u3.getUsername(),password+"1");
}
@Test
public void testHasRole() {
login("classpath:shiro.ini", u1.getUsername(), password );
System.out.println(subject().hasRole("admin"));
}
@Test
public void testNoRole() {
login("classpath:shiro.ini", u2.getUsername(), password);
System.out.println(subject().hasRole("admin"));
}
@Test
public void testHasPermission() {
login("classpath:shiro.ini", u1.getUsername(), password);
Assert.assertTrue(subject().isPermittedAll("user:create", "menu:update"));
}
@Test
public void testNoPermission() {
login("classpath:shiro.ini", u2.getUsername(), password);
Assert.assertFalse(subject().isPermitted("user:create"));
}
}