0. 总体流程
1. 引入依赖
2. 配置工具类
(ShiroFilfter 自定义需要拦截的请求 ——>
需要自己编写JWTFilter,用来验证JWT的token )
(DefaultWebSecurityManager 创建安全管理器,拦截的请求需要用安全管理器来处理)
(Realm 数据源,安全管理器通过数据源定义的规则对请求进行处理 ——>
设置凭证匹配器(md5和散列次数,以及开启缓存))
(ShiroDialect 方言处理类, 解析thymeleaf中的shiro标签)
3. 创建实体类
(用户、角色、权限)
4. 创建自定义realm(继承AuthorizingRealm ,重写认证和授权方法)
(需要自定义随机盐类,因为系统加载随机盐的类没有序列化所以不能用redis)
5. 自定义缓存管理器和缓存类
(不能IOC注入,需要用自定义工具类来获取redisTemplate 的 bean对象)
请求访问-->是否被拦截-->被拦截:通过JWTFilter来验证token是否合法-->合法:访问页面
| |-->不合法(token不存在、token错误、token超时):返回自定义页面
|
|-->一般是登录页,是否登陆成功-->成功:认证和授权subject并存入redis缓存,生成JWT的token返回给前端,前端要把这个token存入request-header,
1.引入依赖
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
<!--shiro和thymeleaf整合-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
2.创建实体类(展示基础字段)
//用户类
public class User {
private Integer id;
private String username;
private String password;
private String salt;
// 角色信息列表
private List<Role> roleList;
// 权限列表
private List<Pers> persList;
}
//角色类
public class Role {
// id
private Integer id;
// 角色名
private String name;
}
//权限类
public class Pers {
// id
private Integer id;
// 权限名
private String name;
// 权限url
private String url;
}
3.编写配置类
@Configuration
public class ShiroConfig {
//创建 ShiroFilfter对象
// 负责拦截所有的请求
// 使用安全管理器来认证和授权
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 添加自定义jwt过滤器,用来验证token
Map<String, Filter> filterMap = shiroFilterFactoryBean.getFilters();
filterMap.put("JWTToken",new JWTFilter());
shiroFilterFactoryBean.setFilters(filterMap);
//配置系统受限的资源
//配置系统的公共资源
Map<String,String> map = new LinkedHashMap<>();
map.put("/user/**","anon"); //开放/user下所有的请求
map.put("/druid/**","anon"); //开放/druid请求
//拦截所有请求,放最后
map.put("/**","JWTToken");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//设置拦截后返回的页面
shiroFilterFactoryBean.setLoginUrl("/");
return shiroFilterFactoryBean;
}
//创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
// 关闭session,没法通过subject获取东西了,只能通过token获取
/*DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
defaultWebSecurityManager.setSubjectDAO(subjectDAO);*/
return defaultWebSecurityManager;
}
//创建自定义Realm
@Bean
public Realm getRealm(){
// 自己定义的Realm
CustomRealm customRealm = new CustomRealm();
// 修改加密的凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置md5加密
credentialsMatcher.setHashAlgorithmName("md5");
// 设置散列次数
credentialsMatcher.setHashIterations(1024);
customRealm.setCredentialsMatcher(credentialsMatcher);
//开启缓存管理
customRealm.setCacheManager(new RedisCacheManger());
customRealm.setCachingEnabled(true); //开启全局缓存
customRealm.setAuthenticationCachingEnabled(true); //开启认证缓存
customRealm.setAuthenticationCacheName("authCCache");
customRealm.setAuthorizationCachingEnabled(true); //开启授权缓存
customRealm.setAuthorizationCacheName("authZCache");
return customRealm;
}
// thymeleaf 中shiro标签的 方言处理器
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
3.1 编写JWTUtil
public class JWTUtil {
// 自定义私钥
private static final String TOKEN_SECRET = "^JDF654e2#^%&HDRe";
//过期时间设置(30分钟)
private static final long EXPIRE_TIME = 30*60*1000;
// 自定义加密
private static final Algorithm ALGORITHM =Algorithm.HMAC256(TOKEN_SECRET);
// 获取token
public static String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
// 过期时间和加密方法
Date date=new Date(System.currentTimeMillis()+EXPIRE_TIME);
// 数据填充
map.forEach((k,v)->{
builder.withClaim(k,v);
});
return builder.withExpiresAt(date).sign(ALGORITHM);
}
// 认证,出现错误会报异常
public static DecodedJWT verifier(String token){
DecodedJWT verify = JWT.require(ALGORITHM).build().verify(token);
return verify;
}
}
3.5 编写JWTFilter
public class JWTFilter extends FormAuthenticationFilter {
@Override
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("token");
System.out.println(token);
try{
JWTUtil.verifier(token);
return true;
}catch (Exception e){
e.printStackTrace();
saveRequestAndRedirectToLogin(request,response);
return false;
}
}
}
4. 编写自定义盐工具
/**
* 解决:
* shiro 使用缓存时出现:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
* 序列化后,无法反序列化的问题
*/
public class CustomSaltUtil implements ByteSource, Serializable {
private static final long serialVersionUID = 1L;
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
public CustomSaltUtil(){
}
public CustomSaltUtil(byte[] bytes) {
this.bytes = bytes;
}
public CustomSaltUtil(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public CustomSaltUtil(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public CustomSaltUtil(ByteSource source) {
this.bytes = source.getBytes();
}
public CustomSaltUtil(File file) {
this.bytes = (new CustomSaltUtil.BytesHelper()).getBytes(file);
}
public CustomSaltUtil(InputStream stream) {
this.bytes = (new CustomSaltUtil.BytesHelper()).getBytes(stream);
}
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public String toHex() {
if(this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
@Override
public String toBase64() {
if(this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
@Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@Override
public String toString() {
return this.toBase64();
}
@Override
public int hashCode() {
return this.bytes != null && this.bytes.length != 0? Arrays.hashCode(this.bytes):0;
}
@Override
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
}
5.编写realm
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 用户授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//查询角色信息
List<Role> list = userService.findRolesListByUsername(((String) principalCollection.getPrimaryPrincipal()));
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//如果不为空
if(!CollectionUtils.isEmpty(list)){
// 把角色信息写入subject
list.forEach(role -> simpleAuthorizationInfo.addRole(role.getName()));
}
//查询权限信息
List<Pers> persList = userService.findPersesListByUsername(((String) principalCollection.getPrimaryPrincipal()));
if(!CollectionUtils.isEmpty(persList)){
persList.forEach(pers -> simpleAuthorizationInfo.addStringPermission(pers.getName()));
return simpleAuthorizationInfo;
}
return null;
}
/**
* 用户认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户名
String principal = (String) authenticationToken.getPrincipal();
// 查询数据库中是否存在这个用户
User user = userService.findUserByUsername(principal);
// 如果查询到了,就和token中的密码对比
if(user != null){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),
new CustomSaltUtil(user.getSalt()),this.getName());
}
return null;
}
}
5.编写control
@RequestMapping("tologin")
public String login(String username,String password){
// 获取subject对象
Subject subject = SecurityUtils.getSubject();
//通过前端来的密码和用户名生成token
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(token);
return "index";
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
} catch (AuthorizationException e) {
log.error("没有权限!", e);
}
return "redirect:/";
}
6 创建缓存
//自定义RedisCache
public class RedisCache<K, V> implements Cache<K, V>{
private String cacheName;
private RedisTemplate redisTemplate;
public RedisCache(){ }
public RedisCache(String s){
cacheName = s;
}
private RedisTemplate getRedisTemplate(){
if(redisTemplate == null)
redisTemplate = ((RedisTemplate) ApplicationContextUtils.getBean("redisTemplate"));
return redisTemplate;
}
@Override
public V get(K k) throws CacheException {
if(k == null || k.equals("")) return null;
return ((V) getRedisTemplate().opsForHash().get(cacheName, k.toString()));
}
@Override
public V put(K k, V v) throws CacheException {
getRedisTemplate().opsForHash().put(cacheName,k.toString(),v);
return null;
}
@Override
public V remove(K k) throws CacheException {
return ((V) getRedisTemplate().opsForHash().delete(cacheName, k.toString()));
}
@Override
public void clear() throws CacheException {
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<K> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<V> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
}
7.创建缓存管理器
public class RedisCacheManger implements CacheManager {
// 参数 s : 缓存的名字
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return new RedisCache<>(s);
}
}
++.标签
<!--获取subject中的用户信息(用户名)-->
<div shiro:principal=""></div>
<!--认证通过展示的内容-->
<div shiro:authenticated="">
authenticated
</div>
<!--认证没有通过展示的内容-->
<div shiro:notAuthenticated="">
notAuthenticated
</div>
<!--授权角色通过展示的内容 admin-->
<div shiro:hasRole="admin">
admin
</div>
<!--授权资源通过展示的内容 user:delete:*-->
<div shiro:hasPermission="user:delete:*">
user:delete:*
</div>
--.工具类
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
//获取bean
public static Object getBean(String name){
return context.getBean(name);
}
}