SaaS-HRM-- Chapter 6 _Shiro advanced authentication and authorization of SaaS-HRM

Application 1Shiro in SpringBoot project

Apache Shiro is a powerful, flexible, open source security framework. It can handle authentication, authorization, session management and enterprise encryption cleanly. More and more companies use as Shiro security framework of the project, to ensure the smooth running of the project.
Prior to explain in just a single use shiro, convenient for students shiro has an intuitive and clear understanding, today we look at shiro
how to use the project as well as other features springBoot

1.1 Case Description

SpringBoot use to build applications, integrate shiro complete framework for user authentication and authorization.
1.1.1 Database Table
Here Insert Picture Description1.1.2 Basic engineering structure
into the base project code data prepared in this project to achieve the basic operation of the user role permissions. We just need to add code to Shiro related operations in this project

1.2 integrity Shiro

1.2.1spring and shiro integration dependent

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>

1.2.2 modify the login method
Certification: authentication / login, verify that the user is not have the appropriate status. Shiro-based authentication, shiro need to collect user login data using the
login method subject into the realm of complete certification.


@RequestMapping(value="/login")
public String login(String username,String password) { try{
Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken uptoken = new
UsernamePasswordToken(username,password);
subject.login(uptoken); return "登录成功";
}catch (Exception e) { return "用户名或密码错误";
}
}

1.2.3 custom realm
Realm domain: Shiro Safety Data (such as users, roles, permissions) from the Realm, that is SecurityManager to authenticate users, it needs to obtain the corresponding user compared to determine the identity of the user is legitimate from the Realm; Realm also require the user the role / permissions to verify that the user can operate; can Realm as dataSource, i.e. secure data source


public class CustomRealm extends AuthorizingRealm {

@Override
public void setName(String name) { super.setName("customRealm");
}

@Autowired
private UserService userService;

/**
* 构造授权方法
*/ @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.获取认证的用户数据
User user = (User)principalCollection.getPrimaryPrincipal();
//2.构造认证数据
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<Role> roles = user.getRoles();
for (Role role : roles) {
//添加角色信息info.addRole(role.getName());
for (Permission permission:role.getPermissions()) {
//添加权限信息info.addStringPermission(permission.getCode());
}
}
return info;
}

/**
* 认证方法
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取登录的upToken
UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken;
//2.获取输入的用户名密码
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
//3.数据库查询用户
User user = userService.findByName(username);
//4.用户存在并且密码匹配存储用户数据
if(user != null && user.getPassword().equals(password)) { return new
SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
}else {
//返回null会抛出异常,表明用户不存在或密码不匹配return null;
}
}
}


1.3Shiro configuration

Shiro SecurityManager is the heart of the architecture for the coordination of multiple components inside the completion of all authentication and authorization process. For example, the login authentication is completed by calling the realm. Use complete SecurityManager, Realm configuration based on assembly springboot


@Configuration
public class ShiroConfiguration {

//配置自定义的Realm @Bean
public CustomRealm getRealm() { return new CustomRealm();
}
//配置安全管理器@Bean
public SecurityManager securityManager(CustomRealm realm) {
//使用默认的安全管理器
DefaultWebSecurityManager securityManager = new
DefaultWebSecurityManager(realm);
//将自定义的realm交给安全管理器统一调度管理securityManager.setRealm(realm); return securityManager;
}

//Filter工厂,设置对应的过滤条件和跳转条件@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
//1.创建shiro过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器filterFactory.setSecurityManager(securityManager);
//3. 通 用 配 置 ( 配 置 登 录 页 面 , 登 录 成 功 页 面 , 验 证 未 成 功 页 面 ) filterFactory.setLoginUrl("/autherror?code=1"); //设置登录页面filterFactory.setUnauthorizedUrl("/autherror?code=2"); //授权失败跳转页面
//4.配置过滤器集合
/**
*key	:访问连接
*支持通配符的形式
*value:过滤器类型
*shiro常用过滤器
*anno	:匿名访问(表明此链接所有人可以访问)
*authc	:认证后访问(表明此链接需登录认证成功之后可以访问)
*/
Map<String,String> filterMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接 顺序判断filterMap.put("/user/home", "anon"); filterMap.put("/user/**", "authc");

//5.设置过滤器filterFactory.setFilterChainDefinitionMap(filterMap); return filterFactory;
}

//配置shiro注解支持@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager); return advisor;
}
}

1.4shiro a filter
Here Insert Picture DescriptionNote: anon, authc, authcBasic, user authentication is first set of filters, perms, port, rest, roles , ssl second filter group authorization, the authorization through the filter, it must first complete login authentication operation (ie, first to complete the certification authorization to go to find) to take the second set of authorized devices
(such as access permissions required roles url, if you have not landed, it will jump directly to the shiroFilterFactoryBean.setLoginUrl (); set url)

1.5 Authorization

Authorization: the authority to verify, verify that a user has authenticated a privilege; that is, to determine whether the user can do things
shiro Support License also supports annotation-based authorization filter
1.5.1-based authorization configured
in the shiro filters can be arranged using the target address request permission

//配置请求连接过滤器配置
//匿名访问(所有人员可以使用)
filterMap.put("/user/home", "anon");
//具有指定权限访问
filterMap.put("/user/find", "perms[user-find]");
//认证之后访问(登录之后可以访问) filterMap.put("/user/**", "authc");
//具有指定角色可以访问
filterMap.put("/user/**", "roles[系统管理员]");

Authorization configuration-based approach, once the operating user does not have permission to operate, the target address will not be executed. It will jump to the specified connection url address. Therefore, treatment needs to address more friendly in connection unauthorized information suggesting
1.5.2 annotation-based authorization
(1) RequiresPermissions
arranged on a methodology to demonstrate that the implementation of this method must have the specified permission

//查询
@RequiresPermissions(value = "user-find")
public String find() { return "查询用户成功";
}

(2) RequiresRoles
configuration to the method, this method is that the operative must have the specified role

//查询
@RequiresRoles(value = "系统管理员")
public String find() { return "查询用户成功";
}

Annotation-based configuration of the authorization, once the operating user does not have permission to operate, the target method will not be executed, and will throw
AuthorizationException exception. So we need to do to complete the unified exception handling unauthorized processing

The 2Shiro Session Management

In shiro all users in the session information will be controlled by Shiro, may be used to provide session shiro JavaSE / JavaEE environment, does not rely on any underlying container, can be used independently, it is a complete session module. Unified session management by Shiro's Session Manager (SessionManager)

2.1 What is shiro session management

SessionManager (Session Manager): Subject of the session, including management of all creation, maintenance and deletion, failure, verification work. SessionManager is a top-level component, managed by the SecurityManager
provides three default shiro achieve:
1.DefaultSessionManager: for JavaSE environment
2.ServletContainerSessionManager: Web environment for direct use servlet container session.
3.DefaultWebSessionManager: for web environment, maintain their own session (session maintains its own direct waste management session Servlet container).
In the web application, after a successful login by shiro Subject.login () method, the user authentication information is actually stored in the HttpSession by the following code verification.

//登录成功后,打印所有session内容@RequestMapping(value="/show")




public String show(HttpSession session) {
// 获取session中所有的键值
Enumeration<?> enumeration = session.getAttributeNames();
// 遍历enumeration中的
while (enumeration.hasMoreElements()) {
// 获取session键值
String name = enumeration.nextElement().toString();
// 根据键值取session中的值
Object value = session.getAttribute(name);
// 打印结果
System.out.println("<B>" + name + "</B>=" + value + "<br>/n");
}
return "查看session成功";
}

2.2 Application Scenes

In a distributed system or micro-service architecture, user authentication is performed through a unified authentication center. If you use the default session management, user information is only stored on a server. Then the other services on the need for a sync session.
Here Insert Picture Description

The session manager can specify and generate sessionId acquisition mode. By sessionDao complete the simulation session stored, removed and other operations

2.3Shiro combination of unified session management redis

2.3.1步骤分析
Here Insert Picture Description
2.3.2构建环境
(1)使用开源组件Shiro-Redis可以方便的构建shiro与redis的整合工程。


<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.0.0</version>
</dependency>

(2)在springboot配置文件中添加redis配置


redis:
host: 127.0.0.1
port: 6379

2.3.3自定义shiro会话管理器

/**
* 自定义的sessionManager
*/
public class CustomSessionManager extends DefaultWebSessionManager {
/**
*头信息中具有sessionid
*请求头:Authorization: sessionid
*
*指定sessionId的获取方式
*/




protected Serializable getSessionId(ServletRequest request, ServletResponse response) {

//获取请求头Authorization中的数据
String id = WebUtils.toHttp(request).getHeader("Authorization");
if(StringUtils.isEmpty(id)) {
//如果没有携带,生成新的sessionId
return super.getSessionId(request,response);
}else{
// 返 回 sessionId; request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
"header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);

request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
}
}

2.3.4配置Shiro基于redis的会话管理
在Shiro配置类 配置
1.配置shiro的RedisManager,通过shiro-redis包提供的RedisManager统一对redis操作


@Value("${spring.redis.host}") private String host;

@Value("${spring.redis.port}") private int port;
//配置shiro redisManager
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setPort(port);
return redisManager;
}

2.Shiro内部有自己的本地缓存机制,为了更加统一方便管理,全部替换redis实现

//配置Shiro的缓存管理器
//使用redis实现
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}

3.配置SessionDao,使用shiro-redis实现的基于redis的sessionDao

/**
*RedisSessionDAO shiro sessionDao层的实现 通过redis
*使用的是shiro-redis开源插件
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}

4.配置会话管理器,指定sessionDao的依赖关系


/**
* 3.会话管理器
*/
public DefaultWebSessionManager sessionManager() { CustomSessionManager sessionManager = new CustomSessionManager(); sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}

5.统一交给SecurityManager管理

//配置安全管理器@Bean
public SecurityManager securityManager(CustomRealm realm) {
//使用默认的安全管理器
DefaultWebSecurityManager securityManager = new
DefaultWebSecurityManager(realm);
// 自 定 义 session 管 理 使 用 redis securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
//将自定义的realm交给安全管理器统一调度管理securityManager.setRealm(realm); return securityManager;
}

3SaaS-HRM中的认证授权

3.1需求分析

实现基于Shiro的SaaS平台的统一权限管理。我们的SaaS-HRM系统是基于微服务构建,所以在使用Shiro鉴权的时 候,就需要将认证信息保存到统一的redis服务器中完成。这样,每个微服务都可以通过指定cookie中的sessionid 获取公共的认证信息。

3.2搭建环境

3.2.1导入依赖
父工程导入Shiro的依赖


<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.0.0</version>
</dependency>

3.2.2配置值对象
不需要存入redis太多的用户数据,和获取用户信息的返回对象一致即可,需要实现AuthCachePrincipali接口


@Setter @Getter
public class ProfileResult implements Serializable,AuthCachePrincipal { private String mobile;
private String username; private String company; private String companyId;
private Map<String,Object> roles = new HashMap<>();
//省略
}

3.2.3配置未认证controller
为了在多个微服务中使用,配置公共的未认证未授权的Controller

@RestController @CrossOrigin
public class ErrorController {

//公共错误跳转@RequestMapping(value="autherror") public Result autherror(int code) {
return code ==1?new Result(ResultCode.UNAUTHENTICATED):new Result(ResultCode.UNAUTHORISE);
}

}

3.2.4 custom realm authorization
to create a common authentication and authorization realm ihrm-common under the module should be noted that, in this realm can only deal with authorized data, authentication method requires login module completion.


public class IhrmRealm extends AuthorizingRealm {

@Override
public void setName(String name) { super.setName("ihrmRealm");
}

//授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection
principalCollection) {
//1.获取安全数据ProfileResult result =
(ProfileResult)principalCollection.getPrimaryPrincipal();
//2.获取权限信息
Set<String> apisPerms = (Set<String>)result.getRoles().get("apis");
//3.构造权限数据,返回值
SimpleAuthorizationInfo info = new	SimpleAuthorizationInfo(); info.setStringPermissions(apisPerms);
return info;
}

/**
* 认证方法
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}

3.3.5 custom session manager
program prior to using the user authentication jwt way, the front end of the rear end of the transmission header is a request token. In order to adapt the program before, in shiro you need to change the acquisition mode of sessionId. Solved, in the session management shiro, you can easily use the contents of the request header as sessionid


public class IhrmWebSessionManager extends DefaultWebSessionManager {

private static final String AUTHORIZATION = "Authorization";

private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

public IhrmWebSessionManager(){ super();
}

protected Serializable getSessionId(ServletRequest request, ServletResponse response){
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if(StringUtils.isEmpty(id)){
//如果没有携带id参数则按照父类的方式在cookie进行获取return super.getSessionId(request, response);
}else{
id = id.replace("Bearer ", "");
//如果请求头中有 authToken 则其值为sessionId

request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_S ESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);

request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TR UE);
return id;
}
}
}

3.3 User Authentication

3.3.1 Configuring User Login

//用户名密码登录
@RequestMapping(value="/login",method = RequestMethod.POST) public Result login(@RequestBody Map<String,String> loginMap) {
String mobile = loginMap.get("mobile"); String password = loginMap.get("password");
try {
//1.构造登录令牌 UsernamePasswordToken
//加密密码
password = new Md5Hash(password,mobile,3).toString();	//1.密码,盐,加密次数
UsernamePasswordToken upToken = new UsernamePasswordToken(mobile,password);
//2.获取subject
Subject subject = SecurityUtils.getSubject();
//3.调用login方法,进入realm完成认证
subject.login(upToken);
//4.获取sessionId
String sessionId = (String)subject.getSession().getId();
//5.构造返回结果
return new Result(ResultCode.SUCCESS,sessionId);
}catch (Exception e) {
return new Result(ResultCode.MOBILEORPASSWORDERROR);
}
}

3.3.2shiro authentication
configured user login authentication realm domain only need to inherit a common authentication method which can complement IhrmRealm

public class UserIhrmRealm extends IhrmRealm {


@Override
public void setName(String name) { super.setName("customRealm");
}


@Autowired
private UserService userService;

//认证方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken
authenticationToken) throws AuthenticationException {
//1.获取用户的手机号和密码
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken; String mobile = upToken.getUsername();
String password = new String( upToken.getPassword());
//2.根据手机号查询用户
User user = userService.findByMobile(mobile);
//3.判断用户是否存在,用户密码是否和输入密码一致
if(user != null && user.getPassword().equals(password)) {
//4.构造安全数据并返回(安全数据:用户基本数据,权限信息 profileResult) ProfileResult result = null; if("user".equals(user.getLevel())) {
result = new ProfileResult(user);
}else {
Map map = new HashMap(); if("coAdmin".equals(user.getLevel())) {
map.put("enVisible","1");
}
List<Permission> list = permissionService.findAll(map); result = new ProfileResult(user,list);
}
//构造方法:安全数据,密码,realm域名SimpleAuthenticationInfo info = new
SimpleAuthenticationInfo(result,user.getPassword(),this.getName()); return info;
}
//返回null,会抛出异常,标识用户名和密码不匹配
return null;
}
}

3.3.3 Gets session data
baseController shiro used to obtain authentication data from the redis


//使用shiro获取@ModelAttribute
public void setResAnReq(HttpServletRequest request,HttpServletResponse response) { this.request = request;
this.response = response;

//获取session中的安全数据
Subject subject = SecurityUtils.getSubject();
//1.subject获取所有的安全数据集合
PrincipalCollection principals = subject.getPrincipals();
if(principals != null && !principals.isEmpty()){
//2.获取安全数据
ProfileResult result = (ProfileResult)principals.getPrimaryPrincipal(); this.companyId = result.getCompanyId();
this.companyName = result.getCompany();
}
}

3.4 User Authorization

@RequiresPermissions disposed on the interface need to use ( "API-USER-DELETE")

3.5 Configuration

Configuration class configuration shiro


@Configuration
public class ShiroConfiguration {

@Value("${spring.redis.host}") private String host;

@Value("${spring.redis.port}") private int port;

//配置自定义的Realm @Bean
public IhrmRealm getRealm() { return new UserIhrmRealm();
}

//配置安全管理器
@Bean
public SecurityManager securityManager() {
//使用默认的安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自 定 义 session 管 理 使 用 redis securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
//将自定义的realm交给安全管理器统一调度管理securityManager.setRealm(getRealm()); return securityManager;
}

//Filter工厂,设置对应的过滤条件和跳转条件@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
//1.创建shiro过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器filterFactory.setSecurityManager(securityManager);
//3. 通 用 配 置 ( 配 置 登 录 页 面 , 登 录 成 功 页 面 , 验 证 未 成 功 页 面 ) filterFactory.setLoginUrl("/autherror?code=1"); //设置登录页面filterFactory.setUnauthorizedUrl("/autherror?code=2"); //授权失败跳转页面
//4.配置过滤器集合
/**
*key	:访问连接
*支持通配符的形式
*value:过滤器类型
*shiro常用过滤器
*anno	:匿名访问(表明此链接所有人可以访问)
*authc	:认证后访问(表明此链接需登录认证成功之后可以访问)
*/
Map<String,String> filterMap = new LinkedHashMap<String,String>();
//配置请求连接过滤器配置
//匿名访问(所有人员可以使用)
filterMap.put("/frame/login", "anon");
filterMap.put("/autherror", "anon");
//认证之后访问(登录之后可以访问) filterMap.put("/**", "authc");

//5.设置过滤器filterFactory.setFilterChainDefinitionMap(filterMap); return filterFactory;
}

//配置shiro注解支持@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager); return advisor;
}

//配置shiro redisManager
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setPort(port);
return redisManager;
}

//cacheManager缓存 redis实现
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}


/**
*RedisSessionDAO shiro sessionDao层的实现 通过redis
*使用的是shiro-redis开源插件
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}


/**
*shiro session的管理
*/
public DefaultWebSessionManager sessionManager() { IhrmWebSessionManager sessionManager = new IhrmWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
}

Guess you like

Origin blog.csdn.net/kai46385076/article/details/92755377