近期对Springboot框架的学习中,为了更好的学习理解Springsecurity中间件,先学习了一下“老派”的shiro安全框架,本文章将通过注解的方式实现基础的用户认证和角色授权案例
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
<properties>
<java.version>1.8</java.version>
<!--shiro-->
<shiro.version>1.3.2</shiro.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!-- SECURITY begin -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- SECURITY end -->
</dependencies>
@TableName("pe_user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(value = "id",type = IdType.AUTO)
private int id;
private String username;
private String password;
private String salt;
@TableField(exist = false)
private Set<Role> roles;
}
@TableName("pe_role")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
@TableId(value = "id",type = IdType.AUTO)
private int id;
private String name;
private String code;
private String description;
@TableField(exist = false)
private Set<Permission> permissions;
}
@TableName("pe_permission")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Permission {
@TableId(value = "id",type = IdType.AUTO)
private int id;
private String name;
private String code;
private String description;
}
public interface UserMapper extends BaseMapper<User> {
@Select("select * from pe_user where username = #{name}")
User findUserByName(String name);
//级联查询
@Results({
@Result(column = "id",property = "id"),
@Result(column = "username",property = "username"),
@Result(column = "password",property = "password"),
@Result(column = "salt",property = "salt"),
@Result(column = "id",property = "roles",
many=@Many(select = "com.apesource.shirostudy_demo_05.dao.RoleMapper.findRoleIdsByUserId"))
})
@Select("select * from pe_user where id = #{id}")
User findUserDetailById(@Param("id") int id);
}
public interface IUserService extends IService<User> {
User findUserByName(String name);
User findUserDetailById(int id);
}
@Service
@SuppressWarnings("all")
public class UserServiceImp extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
UserMapper userMapper;
@Autowired
IRoleService roleService;
@Override
public User findUserByName(String name) {
User user = userMapper.findUserByName(name);
return user;
}
@Override
public User findUserDetailById(int id) {
//根据userid查询user
User user = userMapper.findUserDetailById(id);
//返回
return user;
}
}
@RestController
public class UserController {
@Autowired
private IUserService userService;
//个人主页
@RequiresPermissions("user-home")
@RequestMapping(value = "/user/home")
public String home() {
return "访问个人主页成功";
}
//根据名字查询用户基本信息
@RequiresPermissions("user-find")
@RequestMapping(value = "/user/userByName/{name}")
public String findUserByName(@PathVariable String name) {
System.out.println(name);
User user = userService.findUserByName(name);
return user.toString();
}
//更新
@RequiresPermissions("user-update")
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public String update(@PathVariable String id) {
return "更新用户成功";
}
//删除
@RequiresPermissions("user-delete")
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public String delete(@PathVariable String id) {
return "删除用户成功";
}
//根据id查询用户详细信息
@RequiresPermissions("user-find")
@RequestMapping(value = "/user/userById/{id}")
public String findUserDetailById(@PathVariable int id) {
return userService.findUserDetailById(id).toString();
}
//登录页面
@RequestMapping(value = "/login")
public String login(User user) {
try {
//1.构造登录令牌
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
//2.//2.获取subject
Subject subject = SecurityUtils.getSubject();
//3.调用subject进行登录
subject.login(token);
return "登录成功!!!";
} catch (AuthenticationException e) {
return "用户名或密码错误!!!";
}
}
//未登陆与未授权页面
@RequestMapping(value = "/autherror")
public String autherror() {
return "未登录";
}
}
public interface RoleMapper extends BaseMapper<Role> {
@Results({
@Result(column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "code",property = "code"),
@Result(column = "description",property = "description"),
@Result(column = "id",property = "permissions",
many =@Many(select = "com.apesource.shirostudy_demo_05.dao.PermissionMapper.findPermissionIdsByRoleId"))
})
@Select("SELECT * FROM pe_role WHERE id IN (SELECT role_id FROM pe_user_role WHERE user_id = #{id})")
Set<Role> findRoleIdsByUserId(int id);
}
public interface IRoleService extends IService<Role> {
Set<Role> findRoleIdsByUserId(int id);
}
@Service
@SuppressWarnings("all")
public class RoleServiceImp extends ServiceImpl<RoleMapper, Role> implements IRoleService {
@Autowired
RoleMapper roleMapper;
@Autowired
IPermissionService permissionService;
@Override
public Set<Role> findRoleIdsByUserId(int id) {
Set<Role> roles = roleMapper.findRoleIdsByUserId(id);
return roles;
}
}
由于不演示,无。
public interface PermissionMapper extends BaseMapper<Permission> {
@Results({
@Result(column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "code",property = "code"),
@Result(column = "description",property = "description"),
})
@Select("SELECT * FROM pe_permission WHERE id IN (SELECT permission_id FROM pe_role_permission WHERE role_id = #{id} ) ")
Set<Permission> findPermissionIdsByRoleId(int id);
}
public interface IPermissionService extends IService<Permission> {
Set<Permission> findPermissionIdsByRoleId(int id);
}
@Service
public class PermissionServiceImp extends ServiceImpl<PermissionMapper, Permission> implements IPermissionService {
@Autowired(required = false)
PermissionMapper permissionMapper;
@Override
public Set<Permission> findPermissionIdsByRoleId(int id) {
Set<Permission> permissionIds = permissionMapper.findPermissionIdsByRoleId(id);
return permissionIds;
}
}
由于不演示,无。
public class DigestsUtil {
public static final String SHA1 = "SHA-1";
public static final Integer COUNTS =369;
/**
* @Description sha1方法
* @param input 需要散列字符串
* @param salt 盐字符串
* @return
*/
public static String show(String input, String salt) {
return new SimpleHash(SHA1, input, salt,COUNTS).toString();
}
/**
* @Description 随机获得salt字符串
* @return
*/
public static String generateSalt(){
SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
return randomNumberGenerator.nextBytes().toHex();
}
/**
* @Description 生成密码字符密文和salt密文
* @param
* @return
*/
public static Map<String,String> entryptPassword(String passwordPlain) {
Map<String,String> map = new HashMap<>();
String salt = generateSalt();
String password =show(passwordPlain,salt);
map.put("salt", salt);
map.put("password", password);
return map;
}
}
@ControllerAdvice
public class BaseExceptionHandler {
@ExceptionHandler(value = AuthorizationException.class)
@ResponseBody
public String error(HttpServletRequest request, HttpServletResponse response, AuthorizationException e){
return "未授权!!!";
}
}
public class MyRealm extends AuthorizingRealm {
@Autowired
IUserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
int userId = (int) principalCollection.getPrimaryPrincipal();
User user = userService.findUserDetailById(userId);
Set<String> roles = new HashSet<>();
Set<String> permissions = new HashSet<>();
user.getRoles().stream().forEach(role -> {
roles.add(role.getCode());
role.getPermissions().stream().forEach(permission -> {
permissions.add(permission.getCode());
});
});
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
/**
* 认证方法
* 参数:传递的用户名密码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取登录的用户名密码(token)
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
//2.根据用户名查询数据库
//mybatis情景下:user对象中包含ID,name,pwd(匿名)
User user = userService.findUserByName(username);
if(user != null){
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getId(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),"myRealm");
return info;
}
return null;
}
/**
* @Description 自定义密码比较器
* @param
* @return
* bean标签 init-method属性
*/
@PostConstruct
public void initCredentialsMatcher() {
//指定密码算法
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
//指定迭代次数
hashedCredentialsMatcher.setHashIterations(DigestsUtil.COUNTS);
//生效密码比较器
setCredentialsMatcher(hashedCredentialsMatcher);
}
}
@Configuration
public class ShiroConfiguration {
/**
* 1.创建shiro自带cookie对象
*/
@Bean
public SimpleCookie sessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
//2.创建realm
@Bean
public MyRealm getRealm(){
return new MyRealm();
}
/**
* 3.创建会话管理器
*/
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setGlobalSessionTimeout(3600000);
return sessionManager;
}
//4.创建安全管理器
@Bean
public SecurityManager defaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(getRealm());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 5.保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 6.开启对shiro注解的支持
* AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 7.配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
return authorizationAttributeSourceAdvisor;
}
//8.配置shiro的过滤器工厂再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
@Bean
public ShiroFilterFactoryBean shiroFilter() {
//1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(defaultWebSecurityManager());
//3.通用配置(跳转登录页面,为授权跳转的页面)
filterFactory.setLoginUrl("/autherror");//跳转url地址
//4.设置过滤器集合
//key = 拦截的url地址
//value = 过滤器类型
Map<String,String> filterMap = new LinkedHashMap<>();
//key:请求规则 value:过滤器名称
filterMap.put("/login","anon");//当前请求地址可以匿名访问
filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问
//在过滤器工程内设置系统过滤器
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
}
用户密码均是123456加密后保存至数据库
由于刚刚登录的信息有管理员和员工的身份,所以其所有功能都能使用。
有权限时:
无权限时:
无权限时会被全局异常处理器拦截,并作出响应的响应。
通过对shrio的学习,更好的理解了安全认证以及鉴权授权的流程,大概了解了安全认证的机制,对我之后学习理解Springsecurity有更好的帮助。