1.准备用户数据,这里我将数据写死存储;
public class Constant implements Serializable {
public static final String USERNAME = "gaoyang";
public static final String PASSWORD = "123456";
public static final String ROLE1 = "admin";
public static final String ROLE2 = "guest";
public static Map map;
public static Map get(){
if(map==null){
map = new HashedMap();
}
return map;
}
}
2.配置redis,这里value序列化使用的是jdk自己的,使用json会报错
spring:
redis:
host: 192.168.32.128
port: 6382
password: 123456
jedis:
pool:
max-wait: 1
max-idle: 10
max-active: 8
min-idle: 5
timeout: 5000
@EnableCaching
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(factory);
return stringRedisTemplate;
}
}
3.重写sessionid接受方式,这里我们接收前台传过来的token
public class SessionConfig extends DefaultWebSessionManager {
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader("token");
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
4.重写生成,读取,变更token信息的方法,使用redis保存
@Component
public class SessionDaoConfig extends EnterpriseCacheSessionDAO {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
SimpleSession session1 = (SimpleSession) session;
session1.setId(sessionId);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
Session o = (Session) redisTemplate.opsForValue().get(sessionId);
return o;
}
@Override
protected void doUpdate(Session session) {
if (session instanceof ValidatingSession) {
System.out.println(session.getTimeout());
System.out.println(session.getId());
if (((ValidatingSession) session).isValid()) {
redisTemplate.opsForValue().set(session.getId(), session);
} else {
redisTemplate.delete(session.getId());
}
} else {
redisTemplate.opsForValue().set(session.getId(), session);
}
}
@Override
protected void doDelete(Session session) {
redisTemplate.delete(session.getId());
}
}
5.自定义realm验证方法
@Component("myRealm")
public class MyRealm extends AuthorizingRealm {
@Override
public String getName() {
return "myRealm";
}
//权限认证,通过名字读取角色死数据
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.getPrimaryPrincipal();
System.out.println(username);
SimpleAuthorizationInfo sim = new SimpleAuthorizationInfo();
if (username.equals("gaoyang")) {
sim.addRole(Constant.ROLE1);
} else {
sim.addRole(Constant.ROLE2);
}
return sim;
}
//身份验证,这里密码写死
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
if (username == null) {
return null;
}
SimpleAuthenticationInfo sim = new SimpleAuthenticationInfo(username, Constant.PASSWORD, this.getName());
return sim;
}
/*@Override
public CredentialsMatcher getCredentialsMatcher() {
HashedCredentialsMatcher hash = new HashedCredentialsMatcher();
hash.setHashAlgorithmName("md5");
hash.setHashIterations(2);
return hash;
}*/
}
6.设置shiro权限控制中心
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
@Bean("shirFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiro = new ShiroFilterFactoryBean();
shiro.setLoginUrl("/unlogin"); //没有登陆的json返回
shiro.setUnauthorizedUrl("/no"); //没有权限的json返回
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/ajaxLogin", "anon");
filterChainDefinitionMap.put("/map", "anon");
filterChainDefinitionMap.put("/login", "anon");
//注意,如果roles[admin,guest]是用户需要同时包含两者角色才可以访问,是且的关系;
//如果想改为或的关系,请继承AuthorizationFilter并加入过滤连,perm资源也是一样,需要继承PermissionsAuthorizationFilter加入过滤链;
filterChainDefinitionMap.put("/test", "authc,roles[admin]");
filterChainDefinitionMap.put("/**", "authc");
shiro.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiro.setSecurityManager(securityManager);
return shiro;
}
@Bean("securityManager")
public SecurityManager securityManager(SessionDaoConfig sessionDaoConfig) {
DefaultWebSecurityManager def = new DefaultWebSecurityManager();
def.setRealm(myRealm);
// 自定义session管理 使用redis
SessionConfig sessionConfig = new SessionConfig();
sessionConfig.setSessionDAO(sessionDaoConfig);
//sessionConfig.setSessionDAO(new SessionDaoConfig());
def.setSessionManager(sessionConfig);
// 自定义缓存实现 使用redis
//def.setCacheManager();
return def;
}
}
7.controller层的登陆,退出等
@RestController
public class IndexController {
@Autowired
private RedisTemplate<Object,Object>redisTemplate;
@CrossOrigin
@GetMapping("/login")
public Object login(String username, String password) {
Map<String, Object> map = new HashMap<>();
map.put("code", 200);
map.put("msg", "登录成功");
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
map.put("token", subject.getSession().getId());
try {
subject.login(token);
Session session = subject.getSession();
//这里设置session过期时间
session.setTimeout(25000);
session.setAttribute("currentuser", username);
} catch (Exception e) {
map.put("code", 204);
map.put("msg", "登录失败");
return map;
}
return map;
}
@GetMapping("/unlogin")
public Object unlogin() {
Map<String, Object> map = new HashMap<>();
map.put("code", 204);
map.put("msg", "未登录");
return map;
}
@GetMapping("/test")
public Object test() {
Map<String, Object> map = new HashMap<>();
Subject subject = SecurityUtils.getSubject();
Object currentuser = subject.getSession().getAttribute("currentuser");
map.put("code", 200);
map.put("msg", "查询成功");
map.put("data", currentuser);
return map;
}
@GetMapping("/logout")
public Object logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
Map<String, Object> map = new HashMap<>();
map.put("code", 200);
map.put("msg", "退出成功");
return map;
}
8.自定义的过滤替代roles和perms
public class PermConfig extends PermissionsAuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = this.getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
for (int i = 0; i < rolesArray.length; i++) {
if (subject.isPermitted(rolesArray[i])) {
return true;
}
}
return false;
}
}
public class RoleConfig extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
Subject subject = this.getSubject(servletRequest, servletResponse);
String[] rolesArray = (String[]) o;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
for (int i = 0; i < rolesArray.length; i++) {
if (subject.hasRole(rolesArray[i])) {
return true;
}
}
return false;
}
}
不过作为前后端分离项目,用户的信息及过期权限等信息依然是靠后端存储,以上依然涉及session,只不过是将产生的jsessionid当作token使用,使用redis存储而已.可以考虑使用jwt,彻底是后端无状态化;