shiro介绍:

      Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

三个核心组件:Subject, SecurityManager 和 Realms.

Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
  Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
  SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
  Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
  从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
  Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

下面以springboot + shiro 为例,实现其认证授权过程。

1. 导入依赖(建议取maven仓库去找最新的依赖)

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

2. 编写实体类(共 5张表,三个实体类)

2.1 User用户实体类

/**
 * @Description : User用户实体类
 */
@Entity
@Table(name = "t_user_info")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue
    private Integer uid;
    @Column(unique =true)
    private String username;//帐号
    private String name;//名称(昵称或者真实姓名,不同系统不同定义)
    private String password; //密码;
    private String salt;//加密密码的盐
    private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
    @ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
    @JoinTable(name = "t_sys_user_role", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List<SysRole> roleList;// 一个用户具有多个角色

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public byte getState() {
        return state;
    }

    public void setState(byte state) {
        this.state = state;
    }

    public List<SysRole> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<SysRole> roleList) {
        this.roleList = roleList;
    }

    /**
     * 密码盐.
     * @return
     */
    public String getCredentialsSalt(){
        return this.username+this.salt;
    }
    //重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解
}

2.2 角色实体类

/**
 * @Description : 角色实体类
 */
@Entity
@Table(name = "t_sys_role")
public class SysRole {
    @Id@GeneratedValue
    private Integer id; // 编号
    private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
    private String description; // 角色描述,UI界面显示使用
    private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户

    //角色 -- 权限关系:多对多关系;
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="t_sys_role_permission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
    private List<SysPermission> permissions;

    // 用户 - 角色关系定义;
    @ManyToMany
    @JoinTable(name="t_sys_user_role",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
    private List<UserInfo> userInfos;// 一个角色对应多个用户

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Boolean getAvailable() {
        return available;
    }

    public void setAvailable(Boolean available) {
        this.available = available;
    }

    public List<SysPermission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<SysPermission> permissions) {
        this.permissions = permissions;
    }

    public List<UserInfo> getUserInfos() {
        return userInfos;
    }

    public void setUserInfos(List<UserInfo> userInfos) {
        this.userInfos = userInfos;
    }
}

2.3 权限 实体类

/**
 * @Description : 权限 实体类
 */
@Entity
@Table(name = "t_sys_permission")
public class SysPermission implements Serializable {
    @Id
    @GeneratedValue
    private Integer id;//主键.
    private String name;//名称.
    @Column(columnDefinition="enum('menu','button')")
    private String resourceType;//资源类型,[menu|button]
    private String url;//资源路径.
    private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
    private Long parentId; //父编号
    private String parentIds; //父编号列表
    private Boolean available = Boolean.FALSE;
    @ManyToMany
    @JoinTable(name="t_sys_role_permission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
    private List<SysRole> roles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getResourceType() {
        return resourceType;
    }

    public void setResourceType(String resourceType) {
        this.resourceType = resourceType;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public Long getParentId() {
        return parentId;
    }

    public void setParentId(Long parentId) {
        this.parentId = parentId;
    }

    public String getParentIds() {
        return parentIds;
    }

    public void setParentIds(String parentIds) {
        this.parentIds = parentIds;
    }

    public Boolean getAvailable() {
        return available;
    }

    public void setAvailable(Boolean available) {
        this.available = available;
    }

    public List<SysRole> getRoles() {
        return roles;
    }

    public void setRoles(List<SysRole> roles) {
        this.roles = roles;
    }
}

3. 编写 shiro 核心类 ShiroConfiguration.java 和 MyshiroRealm.java

3.1 ShiroConfiguration.java

@Configuration
public class ShiroConfiguration {

    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //自定义拦截器
        Map<String, Filter> shiroSessionFilterMap = new LinkedHashMap<>();
        //map里面key值要为authc才能使用自定义的过滤器
        shiroSessionFilterMap.put("authc", new XFormAuthenticationFilter());

        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->

        //swagger接口权限 开放
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
        filterChainDefinitionMap.put("/**","authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");

        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index.html");

        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403.html");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        shiroFilterFactoryBean.setFilters(shiroSessionFilterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);//此处决定加密密码转化为16进制(与入库时保持一致),这一行决定hex还是base64
        return hashedCredentialsMatcher;
    }

    /**
     * 将自己的验证方式加入容器
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        //配置自定义session管理,使用ehcache 或redis
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name="simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
        mappings.setProperty("UnauthorizedException","403");
        r.setExceptionMappings(mappings);  // None by default
        r.setDefaultErrorView("error");    // No default
        r.setExceptionAttribute("ex");     // Default is "exception"
        //r.setWarnLogCategory("example.MvcLogger");     // No default
        return r;
    }

    /**
     * 配置保存sessionId的cookie
     * 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie
     * @return
     */
    @Bean("sessionIdCookie")
    public SimpleCookie sessionIdCookie(){
        //这个参数是cookie的名称
        SimpleCookie simpleCookie = new SimpleCookie("sid");
        //setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:

        //setcookie()的第七个参数
        //设为true后,只能通过http访问,javascript无法访问
        //防止xss读取cookie
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //maxAge=-1表示浏览器关闭时失效此Cookie
        simpleCookie.setMaxAge(-1);
        return simpleCookie;
    }

    /**
     * 配置会话管理器,设定会话超时及保存
     * @return
     */
    @Bean("sessionManager")
    public SessionManager sessionManager() {

//        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        MySessionManager sessionManager = new MySessionManager();
        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
//        //配置监听
//        listeners.add(sessionListener());
//        sessionManager.setSessionListeners(listeners);
        sessionManager.setSessionIdCookie(sessionIdCookie());
//        sessionManager.setSessionDAO(sessionDAO());
//        sessionManager.setCacheManager(ehCacheManager());

        //全局会话超时时间(单位毫秒),默认30分钟
        sessionManager.setGlobalSessionTimeout(1800000);
        //是否开启删除无效的session对象  默认为true
        sessionManager.setDeleteInvalidSessions(true);
        //是否开启定时调度器进行检测过期session 默认为true
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        //设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
        //相隔多久检查一次session的有效性
        sessionManager.setSessionValidationInterval(1800000);

        return sessionManager;

    }

    public class MySessionManager extends DefaultWebSessionManager {
        @Override
        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
            HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
            String token = httpServletRequest.getHeader("token");
            System.out.println("token:"+token);
            if(!StringUtils.isEmpty(token)){
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "token");
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return token;

            }else{
                return super.getSessionId(request, response);
            }
        }
    }
}

3.2 MyShiroRealm.java

public class MyShiroRealm extends AuthorizingRealm {
    @Resource
    private IUserInfoService userInfoService;

    /**
     * 角色权限和对应权限添加
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
        //todo 从数据库查询账号拥有的角色
        List<SysRole> roles = userInfoService.findRolesByUsername(userInfo.getUsername());
        for(SysRole role:roles){
            //todo 添加进来角色
            authorizationInfo.addRole(role.getRole());
            //todo 从数据库查询账号拥有的权限
            List<SysPermission> permissions = userInfoService.findPermissionsByUsername(role.getId());
            for(SysPermission p:permissions){
                //todo 添加进来权限
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }

    /**
     *主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        //加这一步的目的是在Post请求的时候会先进认证,然后在到请求
        if (token.getPrincipal() == null) {
            return null;
        }
        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();

        System.out.println(token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userInfoService.findByUsername(username);

        System.out.println("----->>userInfo="+userInfo);
        if(userInfo == null){
            return null;
        }
        System.out.println("sort : "+userInfo.getCredentialsSalt());
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, //用户名
                userInfo.getPassword(), //从数据库获得的密码
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt  采用明文访问时,不需要此句
                getName()  //realm name
        );
        return authenticationInfo;
    }

}

4.UserInfoMapper.xml (service就不贴出来了)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mapper.UserInfoMapper" >
    <!--根据用户名查询用户信息-->
    <select id="findByUsername" resultType="com.entity.UserInfo" parameterType="java.lang.String">
        select
            u.uid,
            u.username,
            u.`name`,
            u.`password`,
            u.salt,
            u.state
        from t_user_info as u
        where username = #{username};
    </select>

    <!--根据用户名查询账号对应的角色名称-->
    <select id="findRolesByUsername" resultType="com.entity.SysRole" parameterType="java.lang.String">
#         select r.id,r.role,r.description,r.available
        select
            u.uid,
            u.`name`,
            u.`password`,
            u.salt,
            u.state,
            u.username,
            r.id,
            r.available,
            r.description,
            r.role
        from t_user_info as u
            INNER JOIN t_sys_user_role as ur on ur.uid = u.uid
            INNER JOIN t_sys_role as r on ur.roleId = r.id
        WHERE  u.username = #{username};

    </select>

    <!--根据roleId查询角色对应的权限名称-->
    <select id="findPermissionsByUsername" resultType="com.entity.SysPermission" parameterType="java.lang.Integer">
        SELECT
            r.id,
            r.available,
            r.description,
            r.role,
            p.`name`,
            p.permission,
            p.url
        FROM
            t_sys_role as r
                INNER JOIN t_sys_role_permission as rp ON r.id = rp.roleId
                INNER JOIN t_sys_permission as p ON p.id = rp.permissionId
        WHERE r.id=#{roleId}

    </select>

    <insert id="insertUser" parameterType="com.entity.UserInfo">
        insert into t_user_info(
        username,password,salt,state,name
        )
        values(
        #{userInfo.username},#{userInfo.password},#{userInfo.salt},#{userInfo.state},#{userInfo.name}
        )
    </insert>

</mapper>

5. UserController.java

/**
 * @Description : TODO用一句话描述此类的作用
 */
@Api(tags = "UserController", description = "登录注册API")
@Controller
public class UserController {

    /**
     * 日志
     */
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @Resource
    private IUserInfoService userInfoService;

    @ApiOperation(value = "index", notes = "index")
    @RequestMapping(value = { "/", "/index" },method = RequestMethod.POST)
    public String index() {
        return "/index";
    }

    @ApiOperation(value = "登录", notes = "登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "username", value = "用户名", paramType = "query", required = true, dataType = "String"),
            @ApiImplicitParam(name = "password", value = "密码", paramType = "query", required = true, dataType = "String")
    })
    @RequestMapping(value="/login",method = {RequestMethod.GET, RequestMethod.POST},produces="text/html;charset=UTF-8")
    public ResponseMsg userLogin(HttpServletRequest request, String username , String password) {

        ResponseMsg responseMsg = new ResponseMsg();
        Subject subject = SecurityUtils.getSubject();//获取当前主体
        //(AOP拦截横切认证),处理登录失败信息
        String exception = (String) request.getAttribute("shiroLoginFailure");
        System.out.println("exception:"+exception);
        String msg = "";
        if (!subject.isAuthenticated()){
            logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"访问失败!-->请先登录!");
            msg = "请先登录!";
            responseMsg.setResult("请先登录!"+msg);
            return responseMsg;
        }
        logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录状态============>>>>>>> "+subject.isAuthenticated());
        if (exception != null) {
            if (UnknownAccountException.class.getName().equals(exception)) {
                logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录失败!-->账号不存在");
                msg = "错误信息:账号不存在";
                responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
                return responseMsg;
            }else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录失败!-->密码不正确");
                msg = "错误信息:密码不正确";
                responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
                return responseMsg;
            }else if ("kaptchaValidateFailed".equals(exception)) {
                logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录失败!-->验证码错误");
                msg = "错误信息:验证码错误";
                responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
                return responseMsg;
            }else {
                msg = "未知错误!";
                System.out.println("else --> "+exception);
                responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
                return responseMsg;
            }
        }
        msg = "验证通过!";
        logger.info(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录成功!"+msg);
        responseMsg.setResult("用户:"+username+" 登录成功!");
        return responseMsg;
    }

    @ApiOperation(value = "注册", notes = "注册")
    @RequestMapping(value = "/registered",method = RequestMethod.POST)
    @ResponseBody
    public ResponseMsg userRegistered(HttpServletRequest request, @RequestBody UserInfoVo user) {
        ResponseMsg responseMsg = new ResponseMsg();
        System.out.println("------用户注册-------");
        //生成盐(部分,需要存入数据库中)
        String sort=new SecureRandomNumberGenerator().nextBytes().toHex();
        //将原始密码加盐(上面生成的盐),并且用md5算法加密2次,将最后结果存入数据库中
        String result = new SimpleHash("MD5", user.getPassword(), user.getUsername()+sort, 2).toString();
        System.out.println(sort+user.getUsername());
        System.out.println(result);
        user.setPassword(result);
        user.setSalt(sort);
        UserInfo userInfo = userVoToInfo(user);
        Integer integer = userInfoService.insertUser(userInfo);
        System.out.println("注册返回值为:" + integer);
        if (integer > 0) {
            responseMsg.setResult("注册成功!");
            responseMsg.setResponseCode("200");
            return responseMsg;
        }else {
            responseMsg.setResult("该用户名已存在,注册失败!");
            responseMsg.setResult("500");
            return responseMsg;
        }
    }

    private UserInfo userVoToInfo(UserInfoVo userInfoVo){
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(userInfoVo.getUsername());
        userInfo.setPassword(userInfoVo.getPassword());
        userInfo.setName(userInfoVo.getName());
        userInfo.setSalt(userInfoVo.getSalt());
        userInfo.setState(userInfoVo.getState());
        userInfo.setRoleList(userInfoVo.getRoleList());
        return userInfo;
    }

    @ApiOperation(value = "根据用户名查询账号对应的角色名称", notes = "根据用户名查询账号对应的角色名称")
    @ApiImplicitParam(name = "username", value = "String", paramType = "query", required = true, dataType = "String")
    @RequestMapping(value = "/findRolesByUsername",method = RequestMethod.POST)
    public List<SysRole> findRolesByUsername(String username){
        List<SysRole> rolesByUsername = userInfoService.findRolesByUsername(username);
        return rolesByUsername;
    }

    @ApiOperation(value = "根据roleId查询角色对应的权限名称", notes = "根据roleId查询角色对应的权限名称")
    @ApiImplicitParam(name = "roleId", value = "int", paramType = "query", required = true, dataType = "int")
    @RequestMapping(value = "/findPermissionsByUsername",method = RequestMethod.POST)
    public List<SysPermission> findPermissionsByUsername(Integer roleId){
        List<SysPermission> permissionsByUsername = userInfoService.findPermissionsByUsername(roleId);
        return permissionsByUsername;
    }
}

5.1 用到了与前端交互的 VO 类,简单实现一个UserInfoVo.java

/**
 * @Description : 前端交互的 UserVo 类
 */
public class UserInfoVo implements Serializable {
    private Integer uid;
    private String username;//帐号
    private String name;//名称(昵称或者真实姓名,不同系统不同定义)
    private String password; //密码;
    private String salt;//加密密码的盐
    private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
   private List<SysRole> roleList;// 一个用户具有多个角色

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public byte getState() {
        return state;
    }

    public void setState(byte state) {
        this.state = state;
    }

    public List<SysRole> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<SysRole> roleList) {
        this.roleList = roleList;
    }

    /**
     * 密码盐.
     * @return
     */
    public String getCredentialsSalt(){
        return this.username+this.salt;
    }
    //重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解
}

6.编写简单前端登陆页面 login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form action="/login" method="post">
    <ul>
        <li>姓 名:<input type="text" name="username" /> </li>
        <li>密 码:<input type="text" name="password" /> </li>
        <!--<li>验证码:<input type="text" name="validateCode" />-->
            <!--<img id="validateCodeImg" src=<%=basePath%>/validateCode"/>-->
            <!--<a href="#" onclick="javascript:reloadValidateCode();">看不清?</a></li>-->
        <li><input type="submit" value="确认" /> </li>
    </ul>
</form>
</body>
</html>

7.准备工作(为了简单测试,就直接在数据库中添加 用户 角色 权限信息,及其中间表信息)

7.1 注册两个用户admin 和 test

spring boot使用UserDetailService身份认证运行项目 springboot登录认证_实体类

7.2 数据库 t_sys_role 表 加入两条角色信息

spring boot使用UserDetailService身份认证运行项目 springboot登录认证_List_02

7.3 权限表 添加权限信息

spring boot使用UserDetailService身份认证运行项目 springboot登录认证_List_03

7.4 用户角色表

spring boot使用UserDetailService身份认证运行项目 springboot登录认证_List_04

7.5 角色权限表

spring boot使用UserDetailService身份认证运行项目 springboot登录认证_Boo_05

8.登录认证测试

登录成功之后跳转index.html页面

登录失败,返回对应失败信息

9.权限检验测试

测试之前需要说明的是,要判断账号是否有对应权限,需要在对应接口添加 @RequiresPermissions,来确认该用户是否具有对应权限,当然前提是在ShiroConfiguration.java开启注解支持

@RequiresPermissions(userInfo:add)

9.1 test用户没有 userInfo:add 权限

结果:没有对应权限,跳转到403.html页面。

9.2 admin用户有 userInfo:add 权限

结果:可以正常访问