(1) pom.xml中添加Shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
(2) 注入Shiro Factory和SecurityManager
Shiro几个核心的类,第一就是ShiroFilterFactory,第二就是SecurityManager,那么最简单的配置就是注入这两个类就ok了
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map <String, String> filterChainDefinitionMap = new LinkedHashMap <String, String>();
//anon:所有url都都可以匿名访问;
//authc: 需要认证才能进行访问;
//user:配置记住我或认证通过可以访问;
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/authenRecharge/**", "anon");
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/interface/**", "anon");
filterChainDefinitionMap.put("/manage/**", "anon");
filterChainDefinitionMap.put("/wicket/**", "anon");
filterChainDefinitionMap.put("/dbwizard/**", "anon");
filterChainDefinitionMap.put("/mallbook/login", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/wicket/resource/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/mallbook/MallHouse");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//这个很重要,将我们自定义的Realm注入到SecurityManager中
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 凭证匹配器
*应为我们身份认证用的密码是加密的,所以需要一个加密算法 ,要是使用明文就不用了
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码; )
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
//身份认证realm;
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new
AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
(3) 身份认证,权限控制
认证实现
Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写获取用户信息的方法。
shiro的认证最终是交给了Realm进行执行了,所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm。
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
public class MallBookShiroRealm extends AuthorizingRealm {
@Autowired
private UpmsUserService userService;
private final static Logger logger = LoggerFactory.getLogger(MallBookShiroRealm.class);
/**
* 权限认证,为当前登录的Subject授予角色和权限
* <p>
* 本例中该方法的调用时机为需授权资源被访问时
* 并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
* 如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
("权限配置-->EpayShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
for (RolesInfo role : userInfo.getRoles()) {
authorizationInfo.addRole(role.getName());
for (UpmsPermission p : role.getPermissions()) {
if (null != p.getPermissionValue() && p.getPermissionValue().length() > 0) {
authorizationInfo.addStringPermission(p.getPermissionValue());
}
}
}
return authorizationInfo;
}
/**
* 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
("正在验证身份...");
// 获取用户的输入的账号.
//将token转换成UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = (String) token.getPrincipal();
System.out.println(token.getCredentials());
// 通过username从数据库中查找 User对象,如果找到,没找到.
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
UserInfo userInfo = userService.findByUsername(username);
System.out.println("----->>userInfo=" + userInfo);
if (null == userInfo) {
return null;
}
// 得到盐值加密后的密码:只用于方便数据库测试,后期不会用到。
return new SimpleAuthenticationInfo(
userInfo,
userInfo.getPassword(),
ByteSource.Util.bytes(userInfo.getSalt()),
getName()
);
}
}
继承AuthorizingRealm主要需要实现两个方法:
doGetAuthenticationInfo();
doGetAuthorizationInfo();
其中doGetAuthenticationInfo主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
SimpleAuthenticationInfo oauthenticationInfo =
return new SimpleAuthenticationInfo(
userInfo,//用户名
userInfo.getPassword(),//密码
ByteSource.Util.bytes(userInfo.getSalt()),//加密的盐
getName()//realm name
);
doGetAuthorizationInfo()是权限控制,当访问到页面的时候,使用了相应的注解或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
主要的类
:SimpleAuthorizationInfo
for (RolesInfo role : userInfo.getRoles()) {
authorizationInfo.addRole(role.getName());//添加角色
for (UpmsPermission p : role.getPermissions()) {
if (null != p.getPermissionValue() && p.getPermissionValue().length() > 0) {
authorizationInfo.addStringPermission(p.getPermissionValue());//添加菜单
}
}
}
到这里的话身份认证权限控制基本是完成了,最后我们在编写一个登录的时候,登录的处理
import java.util.ArrayList;
import java.util.List;
@RestController
public class LoginController{
Logger log= LoggerFactory.getLogger(LoginController.class);
@Autowired
private UpmsUserService userService;
@RequestMapping(value = "/ajaxLogin", method = RequestMethod.POST)
public Object login(String username, String password) {
String passwordNew= RSAUtils.decrypt(password, RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
UsernamePasswordToken token = new UsernamePasswordToken(username, passwordNew);
token.setHost("localhost");
Subject currentUser = SecurityUtils.getSubject();
UpmsResultConstant messageCode = UpmsResultConstant.LOGIN_SUCCESS;
if (null == userService.getUserInfo(token.getUsername())) {
return new UpmsResult(UpmsResultConstant.FAILED, UpmsResultConstant.FAILED.code);
}
try {
currentUser.login(token);
} catch (UnknownAccountException ex) {
messageCode = UpmsResultConstant.LOGIN_ACCOUNT_ERROR;
ex.printStackTrace();
} catch (IncorrectCredentialsException ex) {
messageCode = UpmsResultConstant.LOGIN_PASSWORD_ERROR;
ex.printStackTrace();
} catch (LockedAccountException ex) {
messageCode = UpmsResultConstant.LOGIN_ACCOUNT_LOCKED;
ex.printStackTrace();
} catch (ExcessiveAttemptsException ex) {
messageCode = UpmsResultConstant.LOGIN_EXCESSIVE_ERROR;
ex.printStackTrace();
} catch (AuthenticationException ex) {
messageCode = UpmsResultConstant.LOGIN_AUTHTICATION_FAIL;
ex.printStackTrace();
}
if (currentUser.isAuthenticated()) {
LoginInfo loginInfo = new LoginInfo();
UserInfo userInfo = (UserInfo) currentUser.getPrincipals().getPrimaryPrincipal();
loginInfo.setAvatar(userInfo.getAvatar());
loginInfo.setIntroduction(userInfo.getRole());
loginInfo.setName(userInfo.getUserName());
loginInfo.setToken(currentUser.getSession().getId().toString());
if (null != userInfo.getRoles() && userInfo.getRoles().size() > 0) {
List<String> roles = new ArrayList<>();
for (RolesInfo rolesInfo : userInfo.getRoles()) {
roles.add(rolesInfo.getName());
}
loginInfo.setRole(roles);
} else {
return new UpmsResult(UpmsResultConstant.FAILED, UpmsResultConstant.FAILED.code);
}
return new UpmsResult(UpmsResultConstant.SUCCESS, loginInfo);
} else {
return new UpmsResult(messageCode, 0);
}
}
@RequestMapping(value = "/getUserInfo")
public Object getUserInfo() {
UserInfo userInfo = UserUtils.getUser();
LoginInfo loginInfo = new LoginInfo();
loginInfo.setAvatar(userInfo.getAvatar());
loginInfo.setIntroduction(userInfo.getRole());
loginInfo.setName(userInfo.getRealName());
loginInfo.setToken(userInfo.getToken());
List<String> roles = new ArrayList<String>();
List<String> pms = new ArrayList<String>();
for (RolesInfo rolesInfo : userInfo.getRoles()) {
roles.add(rolesInfo.getTitle());
for (UpmsPermission permission : rolesInfo.getPermissions()) {
pms.add(permission.getPermissionValue());
}
}
loginInfo.setPms(pms);
loginInfo.setRole(roles);
loginInfo.setMchId(userInfo.getUserId().toString());
return new UpmsResult(UpmsResultConstant.SUCCESS, loginInfo);
}
}
这个时候访问http://localhost:9527/#/login就可以跳转到index页面了
此时身份认证是好了,但是权限控制好像还没有作用
还需要两部分:
第一就是开启shiro aop注解支持
在ShiroConfig需要开启shiro aop注解支持.
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
第二就是在controller方法中加入相应的注解:
@RequiresPermissions("sys_user_add")//权限管理
@RestController
@RequestMapping(value = "/manage/user")
public class UserController {
@Autowired
private UpmsUserService userService;
private static Logger log = LoggerFactory.getLogger(UserController.class);
@Autowired
IdWorker idWorker;
@RequiresPermissions("user_manager")
@RequestMapping(value = "/list", method = RequestMethod.POST)
@ResponseBody
public Object list(UpmsUser user) {
return new UpmsResult(
UpmsResultConstant.SUCCESS
, userService.listUser(user)
);
}
@RequiresPermissions("sys_user_edit")
@RequestMapping(value = "/update", method = RequestMethod.POST)
public Object update(UpmsUser upmsUser) {
log.info("-------------------------------");
// 逻辑验证
ComplexResult result = FluentValidator.checkAll()
.doValidate()
.result(ResultCollectors.toComplex());
if (!result.isSuccess()) {
return new UpmsResult(UpmsResultConstant.INVALID_LENGTH, result.getErrors());
}
String passwordNew = "";
if (StringUtils.isNotEmpty(upmsUser.getPassword())) {
passwordNew = RSAUtils.decrypt(upmsUser.getPassword(), RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
// 不允许直接改密码
Object md = new SimpleHash("MD5", passwordNew, upmsUser.getSalt(), 1);
upmsUser.setPassword(md.toString());
upmsUser.setVerifyPassword(DictConstant.USER_HAS_LOGGED);
}
// upmsUser.setPassword(null);
int count = userService.updateUser(upmsUser);
return new UpmsResult(UpmsResultConstant.SUCCESS, count);
}
@RequiresPermissions("sys_user_edit")
@RequestMapping(value = "/updatePassword", method = RequestMethod.POST)
public Object updatePassword(UpmsUser upmsUser) {
String passwordNew = RSAUtils.decrypt(upmsUser.getPassword(), RSAUtils.getPrivateKey(Constant.PWD_PRIVATE_KEY));
// 不允许直接改密码
Object md = new SimpleHash("MD5", passwordNew, ByteSource.Util.bytes(upmsUser.getSalt()), 1);
if (md.toString().equals(UserUtils.getUser().getPassword())) {
return new UpmsResult(UpmsResultConstant.PASSWORD_REPEAT, 0);
}
upmsUser.setPassword(md.toString());
upmsUser.setVerifyPassword(DictConstant.USER_HAS_LOGGED);
int count = userService.updateUser(upmsUser);
return new UpmsResult(UpmsResultConstant.SUCCESS, count);
}
@RequiresPermissions("sys_user_disabled")
@RequestMapping(value = "/locked", method = RequestMethod.POST)
public Object locked(UpmsUser upmsUser) {
int count = userService.updateUser(upmsUser.getUserId(), StatusEnum.FORBIDDEN.getByteVal());
return new UpmsResult(UpmsResultConstant.SUCCESS, count);
}
@RequiresPermissions("sys_user_enabled")
@RequestMapping(value = "/unlock", method = RequestMethod.POST)
public Object unlock(UpmsUser upmsUser) {
int count = userService.updateUser(upmsUser.getUserId(), StatusEnum.NORMAL.getByteVal());
return new UpmsResult(UpmsResultConstant.SUCCESS, count);
}
@RequestMapping(value = "listCurrentUser", method = RequestMethod.POST)
public Object listCurrentUser() {
return new UpmsResult(
UpmsResultConstant.SUCCESS
, userService.selectUserInfoById());
}
}
到此,身份认证及权限管理已完成,如果还要加入缓存机制,还要引入依赖