Shiro入门到整合redis
用户操作进入Security Manager ,Security Manager 通过Authentication认证器想Reaml获取认证数据,通过Authorizer向Reaml获取权限数据
SimpleAccountRealm realm = new SimpleAccountRealm(); @Before public void adduser(){ realm.addAccount("mark", "12345","admin"); }
@Test public void testAuthentication(){ //1.构建SecurtyManager环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(realm); //2.构建主体提交认证请求 SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); //构建认证请求 UsernamePasswordToken token = new UsernamePasswordToken("mark", "12345"); //进行认证 subject.login(token); //判断认证是否成功,认证失败会报异常 System.out.println("isAuthentication :"+subject.isAuthenticated()); //判断是否存在角色 subject.checkRole("admin"); }
这儿采用的是SimpleAccountReal,Shiro还有IniReaml和JdbcReam两种内置的Reaml
IniReaml采用的是配置文件的格式
user.ini:
[users] mark=12345,admin [roles] admin=user:delete
JdbcReam使用的数据库的格式
如果没有自定义查询语句,Shiro则会使用默认的查询语句默认的表格式为:
代码示例如下: 构建数据源:
DruidDataSource dateSource = new DruidDataSource(); { dateSource.setUrl("jdbc:mysql://localhost:3306/test"); dateSource.setPassword("root"); dateSource.setUsername("root"); }
@Test public void testAuthentication(){ //新建JDBCRealm JdbcRealm jdbcRealm = new JdbcRealm(); //添加数据源 jdbcRealm.setDataSource(dateSource); //开启jdbcReaml的权限功能 jdbcRealm.setPermissionsLookupEnabled(true); //1.构建SecurtyManager环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(jdbcRealm); //2.构建主体提交认证请求 SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); //构建认证请求 UsernamePasswordToken token = new UsernamePasswordToken("admin", "testadmin"); //进行认证 subject.login(token); //判断认证是否成功,认证失败会报异常 System.out.println("isAuthentication :"+subject.isAuthenticated()); //判断是否存在角色 subject.checkRole("admin"); //判断是否具有某个权限 subject.checkPermission("user:delete"); }
自定义Realm
1、自定义Realm需要继承AuthorizingRealm这个类
public class CustomRealm extends AuthorizingRealm{ /** * 授权方法 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取主体传来的已认证信息 String username = (String) principalCollection.getPrimaryPrincipal(); //获取对象的角色 Set<String> roles = getRolesByUsername(username); //获取对象的权限 Set<String> permissions = getPermissionsByUsername(username); //构造返回值 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setRoles(roles); simpleAuthorizationInfo.setStringPermissions(permissions); return simpleAuthorizationInfo; } /** * 认证方法 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //从主体转过来的认证信息中获取用户名 String username = (String) authenticationToken.getPrincipal(); //通过用户名到数据库中获取凭证 String password = getpasswordbyUserName(username); if(password==null){ return null; } //构造返回值 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("cjl",password,"customRealm"); return authenticationInfo; } }
用法和上面的一致。
Shiro 加密
认证时使用如下:
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("md5");//加密方式 matcher.setHashIterations(1);//加密次数 customRealm.setCredentialsMatcher(matcher);//在realm中添加加密方式
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("cjl",password,"customRealm");//在自定义的Realm认证方式的中添加 authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("cjl"));
如果需要加盐
Shiro整合Spring
步骤1、在web.xml中添加shiro的filter
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
步骤2、在spring的配置文件中shiro的过滤器
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="login.html"/> <property name="unauthorizedUrl" value="403.html"/> <property name="filterChainDefinitions"> <value> /login.html = anon /sublogin = anon /* = authc </value> </property> </bean>
参数解释:
anon:不需要认证,直接可以访问
authc:需要认证后才可以访问
后面shiro过滤器部门详解
步骤3、创建SecurityManager对象
<!-- 创建SecurityManager对象--> <bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager"> <property name="realm" ref="customRealm"/> </bean>
步骤4、创建realm对象,可以使用自定义realm
<bean class="com.shiro.realm.CustomRealm" id="customRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!-- 创建加密方式对象--> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id="credentialsMatcher"> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="1"/> </bean>
步骤5、登陆代码如下:
@RequestMapping(value = "/sublogin",produces = "application/json;charset=utf-8") @ResponseBody public String subLogin(User user){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword()); try { subject.login(token); } catch (AuthenticationException e) { return e.getMessage(); } try { subject.checkPermission("user:delete"); } catch (AuthorizationException e) { return String.format("没有权限%s", e.getMessage()); } return "登录成功,拥有权限"; }
Shiro注解配置授权
步骤1、添加pom依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
步骤2、在spring配置文件中添加shiro的aop
<aop:config proxy-target-class="true"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
步骤4、在需要进行认真的controller上添加注解
@RequiresRoles("admin")
@RequiresPermissions("user:delete")
Shiro过滤器
shiro内置的过滤器
认证相关的:anon(不需要认证),authBasic,authc(需要认证),user(需要当前存在用户),logout(需要登出)
权限相关:perms(权限格式["user:delete","user"add"]),roles(角色相关,用法同),ssl(要求安全协议:https),port(要求端口,格式同上)
自定义Filter
如果和授权相关,继承AuthorizationFilter,和认证相关则继承AuthenticatingFilter
代码示例:
public class RolesOrFilter extends AuthorizationFilter{ @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { Subject subject = getSubject(servletRequest,servletResponse ); String[] roles = ((String[]) o); if (roles == null && roles.length <= 0) { for (String role : roles) if (subject.hasRole(role)) { return true; } return false; } else { return true; } } }
在spring的配置文件中进行配置
1、添加该过滤器
<bean class="com.shiro.controller.filter.RolesOrFilter" id="rolesOrFilter"/>
2、将这个过滤器配置到shiro过滤器中
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="login.html"/> <property name="unauthorizedUrl" value="403.html"/> <property name="filterChainDefinitions"> <value> /login.html = anon /sublogin = anon /testRolesOr = rolesOr["user:delete","user:add"] /* = authc </value> </property> <property name="filters"> <util:map> <entry key="rolesOr" value-ref="rolesOrFilter"/> </util:map> </property> </bean>
完成。
Shiro会话管理和缓存管理
会话管理,shiro使用的是AbstractSessionDAO,自定义的时候需要继承这个类,实现其中的方法,比如采用redis的方法。
public class RedisSessionDao extends AbstractSessionDAO { @Resource private JedisUtil jedisUtil; private final String SHIRO_SESSION_PREFIX= "test-session"; private byte[] getkey(String key){ return (String.format("%s%s", SHIRO_SESSION_PREFIX, key)).getBytes(); } @Override protected Serializable doCreate(Session session) { Serializable sessionId = generateSessionId(session); //绑定sessionId和session assignSessionId(session,sessionId ); redisSaveSession(session); return sessionId; } private void redisSaveSession(Session session) { if (session!=null&&session.getId()!=null) { byte[] key = getkey(session.getId().toString()); byte[] value = SerializationUtils.serialize(session); jedisUtil.set(key,value); jedisUtil.expire(key,600); } } @Override protected Session doReadSession(Serializable sessionId) { if (sessionId==null) { return null; } byte[] key = getkey(sessionId.toString()); byte[] value = jedisUtil.getkey(key); return (Session) deserialize(value); } @Override public void update(Session session) throws UnknownSessionException { redisSaveSession(session); } @Override public void delete(Session session) { if (session==null || session.getId()==null) { return; } jedisUtil.del(getkey(session.getId().toString())); } @Override public Collection<Session> getActiveSessions() { Set<byte[]> keys = jedisUtil.keys(SHIRO_SESSION_PREFIX); Set<Session> sessions = new HashSet<>(); if (!isEmpty(keys)) { for (byte[] key : keys) { Session session = (Session) SerializationUtils.deserialize(key); sessions.add(session); } return sessions; } else { return sessions; } } }
在spring的配置文件中进行配置
<bean class="com.shiro.session.CustomSessionManager" id="sessionManager"> <property name="sessionDAO" ref="redisSessionDao"/> </bean> <bean class="com.shiro.session.RedisSessionDao" id="redisSessionDao"/>
<!-- 创建SecurityManager对象--> <bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager"> <property name="realm" ref="customRealm"/> <property name="sessionManager" ref="sessionManager"/> <property name="cacheManager" ref="cacheManager"/> </bean>
使用redis的时候需要重新实现DefaultWebSessionManager,因为每个请求都会去向redis发送请求查询session,对redis造成很大的压力,重新实现DefaultWebSessionManager类,继承这个类,重载其中的retrieveSession方法,第一次请求的时候将session放到request域中。
缓存管理
缓存管理和会话管理类似,代码如下
public class RedisCacheManager implements CacheManager{ @Resource private RedisCache redisCache; @Override public <K, V> Cache<K, V> getCache(String s) throws CacheException { return redisCache; } }
@Component public class RedisCache<K,V> implements Cache<K,V> { @Resource private JedisUtil jedisUtil; private final static String CACHE_PREFIX = "test-cache:"; private byte[] getkey(K k){ if(k instanceof String){ return (CACHE_PREFIX + k).getBytes(); }else{ return SerializationUtils.serialize(k); } } @Override public V get(K k) throws CacheException { byte[] value = jedisUtil.getkey(getkey(k)); if(value!=null){ System.out.println("从redis中获取数据"); return (V) SerializationUtils.deserialize(value); } return null; } @Override public V put(K k, V v) throws CacheException { byte[] key = getkey(k); byte[] value = SerializationUtils.serialize(v); jedisUtil.set(key,value ); jedisUtil.expire(key,600 ); return v; } @Override public V remove(K k) throws CacheException { byte[] key = getkey(k); byte[] value = jedisUtil.getkey(key); jedisUtil.del(key); if(value!=null){ return (V) SerializationUtils.deserialize(value); } return null; } @Override public void clear() throws CacheException { } @Override public int size() { return 0; } @Override public Set<K> keys() { return null; } @Override public Collection<V> values() { return null; } }
spring的配置文件中进行配置
<bean class="com.shiro.cache.RedisCacheManager" id="cacheManager"/>
完成。
详细代码见:
https://github.com/caojinlin/shiroTest.git