习惯用eclipse开发
创建springboot项目前,先安装spring tool插件
打开 help->Eclipse Marketplace
搜索spring找到Spring Tools 3 Add-On插件安装,并重启eclipse
这时候新建项目可以找到Spring Boot选项选择New Spring Starter Project
填写项目名等信息
Next进入依赖选择,一般都会用到Web依赖
点击Finish创建项目,可以在根路径下找到xxxApplication.java的启动类,执行main方法就可以启动项目了
下面说明shiro的配置
首先引入依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.2.3version>
dependency>
使用@Configuration与@Bean注解,添加shiro基本配置
Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)
securityManager是必须的。
在shiroFilter中:
loginUrl:没有登录的用户请求需要登录的页面时自动跳转到登录页面。
successUrl:登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
unauthorizedUrl:没有权限默认跳转的页面。
如果需要自定义一个过滤器
为了保持过滤器的执行顺序,创建一个LinkedHashMap
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager());
shiroFilter.setLoginUrl("/xxx");
shiroFilter.setUnauthorizedUrl("/user/unauthorized");
Map<String, String> filterChain = new LinkedHashMap<>();
filterChain.put("/user/logout", "logout");
filterChain.put("/user/login", "anon");
filterChain.put("/user/register", "anon");
filterChain.put("/user/unauthorized", "anon");
// filterChain.put("/**", "anon");
filterChain.put("/**", "token");
shiroFilter.setFilterChainDefinitionMap(filterChain);
Map<String, Filter> filters = new LinkedHashMap<>();
// filters.put("urlPerms", permFilter());
filters.put("token", tokenFilter());
shiroFilter.setFilters(filters);
return shiroFilter;
}
/**
* 此处不应将自定义Filter注册为 @Bean 否则SpringBoot将加载此Filter导致ShiroFilter优先级失效等一系列问题
* http://www.hillfly.com/2017/179.html
* @return
*/
public MyTokenFilter tokenFilter() {
return new MyTokenFilter();
}
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
@Bean
public MyShiroRealm shiroRealm() {
MyShiroRealm realm = new MyShiroRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
//没有配置权限缓存,所以关闭授权缓存域
realm.setAuthorizationCachingEnabled(false);
return realm;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
* HashedCredentialsMatcher说明:
* 用户传入的token先经过shiroRealm的doGetAuthenticationInfo方法
* 此时token中的密码为明文。
* 再经由HashedCredentialsMatcher加密password与查询用户的结果password做对比。
* new SimpleHash("SHA-256", password, null, 1024).toHex();
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("SHA-256");//散列算法:这里使用SHA-256算法;
hashedCredentialsMatcher.setHashIterations(1024);//散列的次数,比如散列两次,相当于 MD5(MD5(""));
return hashedCredentialsMatcher;
}
}
另外我们还需要自定义ShiroRealm
realm,即 域。
SecurityManager通过realm来验证用户的身份和权限
doGetAuthorizationInfo为授权方法-用于查询用户权限、赋权
doGetAuthenticationInfo为认证方法-subject.login(token);执行时验证用户名密码
public class MyShiroRealm extends AuthorizingRealm{
static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) arg0;
String username = token.getUsername();
logger.info("username = {}", username);
User user = userService.getUserByName(username);
logger.info("{}", null!=user?user.toJson():"null");
if(null != user) {
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
return info;
}
return null;
}
}
另外我还用到了一个过滤器,用作权限验证
isAccessAllowed方法中检验用户是否有权限获取某一资源
AntPathMatcher实现了路径通配符,例如user下所有资源 /user/**
这里使用cookie和缓存来校验用户
仅做参考
当权限被拒绝的时候,执行onAccessDenied
由于这个过滤器在shiro配置中没有注册
使用spring bean的时候需要用到通过ApplicationContext获取bean
public class MyTokenFilter extends AccessControlFilter {
/**
* 项目启动,该类在bean注册前初始化,会报空指针, 所以, 需要使用的时候,在代码中用SpringUtil注入。
*/
private RoleUrlService roleUrlService;
private UserService userService;
private EhcacheUtil ehcacheUtil;
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
String permission = WebUtils.getPathWithinApplication(WebUtils.toHttp(request)).substring(0);
if (StringUtils.isEmpty(permission)) {
return true;
}
// 公共权限验证
roleUrlService = SpringUtil.getBean(RoleUrlService.class);
List<String> publicRole = roleUrlService.getPublicRole();
PatternMatcher matcher = new AntPathMatcher();
for (String uri : publicRole) {
if (null != uri && matcher.matches(uri, permission)) {
return true;
}
}
// Token验证
HttpServletRequest rq = (HttpServletRequest) request;
Cookie token = null;
Cookie[] cookies = rq.getCookies();
if(null == cookies) {
return false;
}
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())) {
token = cookie;
break;
}
}
if (null == token) {
return false;
}
ehcacheUtil = SpringUtil.getBean(EhcacheUtil.class);
UsernamePasswordToken upToken = (UsernamePasswordToken) ehcacheUtil
.get(EhCacheConstants.TOKEN_PREFIX + token.getValue());
if (null == upToken) {
return false;
}
userService = SpringUtil.getBean(UserService.class);
List<String> urlList = userService.findUserUrl(upToken.getUsername());
for (String uri : urlList) {
if (null != uri && matcher.matches(uri, permission)) {
return true;
}
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if (!subject.isAuthenticated()) {
authenticationFailed(response);
return false;
}
return true;
}
/**
* 认证失败
*
* @param response
* @throws IOException
*/
private void authenticationFailed(ServletResponse response) throws IOException {
response.setContentType("text/html;charset=UTF-8");
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.getWriter().write(JSON.toJSONString(Result.notLogin()));
}
}
最后我们需要一个登陆方法
@PostMapping("login")
public Result<String> login(HttpServletResponse response, UsernamePasswordToken token) {
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
UUID uuid = UUID.randomUUID();
// 把用户登录信息存入缓存 key值为 TOKEN_{用户标识}
ehcacheUtil.put(EhCacheConstants.TOKEN_PREFIX + uuid.toString(), token);
response.addCookie(new Cookie("token", uuid.toString()));
return new Result<>("登录成功");
} catch (IncorrectCredentialsException e) {
return new Result<>("密码错误");
} catch (LockedAccountException e) {
return new Result<>("登录失败,该用户已被冻结");
} catch (AuthenticationException e) {
return new Result<>("该用户不存在");
} catch (Exception e) {
e.printStackTrace();
}
return new Result<>("登录错误");
}