Spring+JPA+Struts2和Shrio技术的简单整合,(关于用户登陆方面的)

来源:互联网 发布:生产管理者常用数据 编辑:程序博客网 时间:2024/06/06 04:43

基于maven的开发:

使用shiro的话, 肯定是三大框架的基础整合起码是已经整合完成了的. 下面就是进行加入.

首先在pom.xml 中添加依赖

<!-- 权限控制 框架 --><dependency>    <groupId>org.apache.shiro</groupId>    <artifactId>shiro-all</artifactId>    <version>${shiro.version}</version></dependency>

然后写一个关于使用shiro框架的配置文件:
applicationContext-shiro.xml:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"       xmlns:jpa="http://www.springframework.org/schema/data/jpa"       xmlns:task="http://www.springframework.org/schema/task"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/data/jpahttp://www.springframework.org/schema/data/jpa/spring-jpa.xsd">    <!--配置shiro过滤器-->    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">        <!--securityMannager, 安全管理器-->        <property name="securityManager" ref="securityManager"/>        <!--未认证时候跳转到的页面,自定义的-->        <property name="loginUrl" value="/login.jsp"/>        <!--认证之默认跳转到的页面-->        <property name="successUrl" value="/index.jsp"/>        <!--登陆之后, 判断了用户没有对应权限之后要跳转到的页面-->        <property name="unauthorizedUrl" value="/unauthorized.html"/>        <!--shiro,URL控制过滤器的规则,即该对哪些请求资源进行拦截,或者不拦截-->        <property name="filterChainDefinitions">            <!--以下的一些规则, 都是在shiro框架的Filtet中已经被设定好了的-->            <!--user,表示需要特定的角色才能被访问打到-->            <!--reset, 表示根据指定的请求方式才能被访问-->            <value>                <!--anon, 表示 未登录也可以访问的页面或者资源-->                <!--login.jsp是一个页面, *表示在该页面中可以输入参数-->                /login.jsp* = anon                <!--**, 表示该文件夹下的所有文件都可以被访问-->                /css/** = anon                /js/** = anon                <!--由于我们需要在登陆时候去访问user_login这个Action中的方法(路径),在这里就需要对这个访问放行.-->                <!--*   表示可以在访问这个资源的时候传入一些参数-->                /user_login.action* = anon                <!--如果有CXF访问的话, 需要将它的/service*也配置成不需要拦截,没有就不用写-->                /service/** = anon                /logout = logout                <!--roles, 表示需要特定的角色才能进行访问.-->                /pages/** = roles[admin]                <!--perms, 表示需要特定的权限才能进行访问  访问pages文件夹下的所有资源,/pages/home.jsp*表示需要look权限才能访问这个文件夹下的jsp文件 -->                /pages/** = perms[permssion:look]                <!--当登陆的用户去访问我们限制了固定角色和权限才能访问的页面的时候, shiro会去调用我们自定义的Realm里面的授权方法-->                <!--如果用户不满足角色或者相应的权限,则会自动跳转到                登陆之后, 判断了用户没有对应权限之后要跳转到的页面-->                <!--<property name="unauthorizedUrl" value="/unauthorized.html"/>-->                <!--authc, 表示认证后才可以进行访问, 否则拒绝访问, /**, 这里表示只要认证以后就可以访问所有页面-->                /** = authc            </value>        </property>    </bean>    <!--配置shiro的安全管理器-->    <!--配置安全管理器需要给它注入 Realm,-->    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">        <!-- 单realm应用。如果有多个realm,使用‘realms’属性代替 -->        <!--<property name="realm" ref="sampleRealm"/>-->        <!--<property name="cacheManager" ref="cacheManager"/>-->        <!--注入安全管理器需要调用的Realm. 我们自定义的-->        <property name="realm" ref="myrealm"></property>    </bean>    <!--配置shiro的一个后处理器-->    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean></beans>

上面配置完成了shiro和三大框架的基础融合.

下面就是具体代码.


准备三个实体类(即在数据库中起码要有三张表格. 用户, 角色, 权限.)

package com.cn.hnust.domain;import java.util.Date;import java.util.HashSet;import java.util.Set;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.Table;/** * @description:后台用户 */@Entity@Table(name = "T_USER")public class User {@Id@GeneratedValue@Column(name = "C_ID")private int id; // 主键@Column(name = "C_BIRTHDAY")private Date birthday; // 生日@Column(name = "C_GENDER")private String gender; // 性别@Column(name = "C_PASSWORD")private String password; // 密码@Column(name = "C_REMARK")private String remark; // 备注@Column(name = "C_STATION")private String station; // 状态@Column(name = "C_TELEPHONE")private String telephone; // 联系电话@Column(name = "C_USERNAME", unique = true)private String username; // 登陆用户名@Column(name = "C_NICKNAME")private String nickname; // 真实姓名@ManyToMany@JoinTable(name = "T_USER_ROLE", joinColumns = {@JoinColumn(name = "C_USER_ID", referencedColumnName = "C_ID") }, inverseJoinColumns = {@JoinColumn(name = "C_ROLE_ID", referencedColumnName = "C_ID") })private Set<Role> roles = new HashSet<Role>(0);public int getId() {return id;}public void setId(int id) {this.id = id;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getRemark() {return remark;}public void setRemark(String remark) {this.remark = remark;}public String getStation() {return station;}public void setStation(String station) {this.station = station;}public String getTelephone() {return telephone;}public void setTelephone(String telephone) {this.telephone = telephone;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public Set<Role> getRoles() {return roles;}public void setRoles(Set<Role> roles) {this.roles = roles;}}


package com.cn.hnust.domain;import java.util.HashSet;import java.util.Set;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.Table;/** * @description:角色 */@Entity@Table(name = "T_ROLE")public class Role {@Id@GeneratedValue@Column(name = "C_ID")private int id;@Column(name = "C_NAME")private String name; // 角色名称@Column(name = "C_KEYWORD")private String keyword; // 角色关键字,用于权限控制@Column(name = "C_DESCRIPTION")private String description; // 描述@ManyToMany(mappedBy = "roles")private Set<User> users = new HashSet<User>(0);@ManyToMany@JoinTable(name = "T_ROLE_PERMISSION", joinColumns = {@JoinColumn(name = "C_ROLE_ID", referencedColumnName = "C_ID") }, inverseJoinColumns = {@JoinColumn(name = "C_PERMISSION_ID", referencedColumnName = "C_ID") })private Set<Permission> permissions = new HashSet<Permission>(0);@ManyToMany@JoinTable(name = "T_ROLE_MENU", joinColumns = {@JoinColumn(name = "C_ROLE_ID", referencedColumnName = "C_ID") }, inverseJoinColumns = {@JoinColumn(name = "C_MENU_ID", referencedColumnName = "C_ID") })private Set<Menu> menus = new HashSet<Menu>(0);public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getKeyword() {return keyword;}public void setKeyword(String keyword) {this.keyword = keyword;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public Set<User> getUsers() {return users;}public void setUsers(Set<User> users) {this.users = users;}public Set<Permission> getPermissions() {return permissions;}public void setPermissions(Set<Permission> permissions) {this.permissions = permissions;}public Set<Menu> getMenus() {return menus;}public void setMenus(Set<Menu> menus) {this.menus = menus;}}


package com.cn.hnust.domain;import java.util.HashSet;import java.util.Set;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.ManyToMany;import javax.persistence.Table;/** * @description:权限名称 */@Entity@Table(name = "T_PERMISSION")public class Permission {@Id@GeneratedValue@Column(name = "C_ID")private int id;@Column(name = "C_NAME")private String name; // 权限名称@Column(name = "C_KEYWORD")private String keyword; // 权限关键字,用于权限控制@Column(name = "C_DESCRIPTION")private String description; // 描述@ManyToMany(mappedBy = "permissions")private Set<Role> roles = new HashSet<Role>(0);public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getKeyword() {return keyword;}public void setKeyword(String keyword) {this.keyword = keyword;}public Set<Role> getRoles() {return roles;}public void setRoles(Set<Role> roles) {this.roles = roles;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}}



用户和角色是多对多关系.

角色和权限是多对多关系.

所以实际上,在数据库中还有两张外键表.


先写一个action.在里面有一个验证是否有该用户的login方法.

package com.cn.hnust.action;import com.opensymphony.xwork2.ActionSupport;import com.opensymphony.xwork2.ModelDriven;import com.taotao.domain_code.User;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.apache.struts2.convention.annotation.Action;import org.apache.struts2.convention.annotation.Namespace;import org.apache.struts2.convention.annotation.ParentPackage;import org.apache.struts2.convention.annotation.Result;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Controller;import com.cn.webservice_service.UsersService;@ParentPackage("json-default")@Namespace("/")@Controller@Scope("prototype")public class UserAction extends ActionSupport implements ModelDriven<User> {    //    利用模型驱动封装从前台传递过来的用户名和密码.    private User user = new User();    @Override    public User getModel() {        return user;    }    //    注入需要使用的Service    @Autowired    private UsersService usersService;    //    使用注解指明请求路径, 以及接收返回值之后的动作    @Action(value = "user_login", results = {            @Result(name = "success", type = "redirect", location = "index.jsp"),            @Result(name = "login", type = "redirect", location = "login.jsp")}    )//    具体的方法.    public String login() {//          根据用户名和密码不再直接查询数据库表, 而是让shiro这个框架去查, 查到的结果也是告诉shiro框架.//        基于shiro进行登陆, 需要使用Subject的.login(token)进行登陆//        SecurityUtils这个类来源于org.apache.shiro这个包里面,用于获取一个subject对象        Subject subject = SecurityUtils.getSubject();//      实例化UsernamePasswordToken(来源于org.apache.shiro.authc这个包里面)传入用户名和参数,获取到一个token.        AuthenticationToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());//      调用Subject的登陆验证(login)方法,传入token.如果认证不成功(即数据库中没有这个用户), 则出现异常, return  LOGIN;//        成功则return SUCCESS;        try {//            subject.login(token);这个方法一调用, 就执行shiro框架中, 我们在配置文件中配置的安全管理器(securityManager).//            安全管理器(securityManager).会调用它里面的Realm, 而Realm则要靠我们自己定义一个类并继承AuthorizingRealm.//            查询数据库的方法, 以及告诉shiro,该用户是谁.它拥有哪些权限.//            shiro根据得到的信息, 允许该用户请求哪些资源.            subject.login(token);            return SUCCESS;        } catch (AuthenticationException e) {            e.printStackTrace();//            如果验证之后发现不存在用户, 则会报错. 在抛异常这里处理的就是验证失败后的处理.            return LOGIN;        }    }}


在调用这个login()方法之后,在这个方法里面调用的就是shiro框架提供的方法subject.login(). 调用该方法之后 , shiro框架会自动找到其核心控制---

安全管理器(securityManager). 安全管理器会找我们给它配置的自定义的Realm. 在xml文件中配置给安全管理器的.


接下来就是我们自己自定义一个Realm类, 并继承AuthorizingRealm.

在这个自定义的Realm, 去调用service业务层. 去表中,查找是否有该用户.

package com.cn.webservice_service;import com.cn.hnust.domain.Permission;import com.cn.hnust.domain.Role;import com.cn.hnust.domain.User;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authc.UsernamePasswordToken;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.subject.Subject;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import java.util.List;//自定义的Realm,实现认证方法, 即subject调用了login()之后, 就到这儿来连接数据库查找//自定义的Realm需要继承AuthorizingRealm并实现它里面的两个方法.//想要这个自定义的Realm能被shiro的安全管理器调用, 就需要使用Spring框架的@Service注解进行标识.完成整合@Service("myrealm")//和Spring整合的话一定要设置@Servicepublic class Myrealm extends AuthorizingRealm {    //    注入业务层//    用户(校验用户是否存在的业务层)    @Autowired    private UsersService usersService;    //    角色    @Autowired    private RolesService rolesService;    //    权限    @Autowired    private PermissionService permissionService;    @Override//    授权的方法,所谓授权的方法,就是拿已经认证成功的用户去数据库表中进行查询,看它在表中是什么角色,以及该角色拥有什么权限    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//          该授权方法最终需要返回一个AuthorizationInfo实例对象, AuthorizationInfo是接口, 只能返回它的实现类.先创建该对象.//        该对象为授权对象, 我们需要将在数据库中查找到的所有角色或者权限全都赋给这个对象//         只有这样. shiro框架才能治知道这个认证成功的用户是什么角色, 以及知道这个角色拥有着哪些权限.        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();//        首先, 我们要明确, 这个授权方法的调用是在调用了认证并且认证通过成功之后才会被调用的(还是由安全管理器运行).//        在进行认证的时候, 当判断成功,我们就将User信息已经存到了://        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());这个对象中, User的位置实际上是Object principal.//        public SimpleAuthenticationInfo( Object principal, Object credentials, String realmName),可以通过一个Subject对象直接获取这个对象.        Subject subject = SecurityUtils.getSubject();        User user = (User) subject.getPrincipal();//        接着调用业务层, 查询该用户对应的 角色, 得到对应的很多角色.        List<Role> roles = rolesService.findByUser(user);//        查到该用户对应的所有权限之后, 将该用户对应的所有角色告诉shiro框架.即将每个角色都赋值给SimpleAuthorizationInfo这个要返回的对象//        Role是一个角色对象, 该对象中有一个字段:private String keyword;表明的就是该角色的身份.//        addRole()方法里面传入的参数必须是一个String类型.        for (Role role : roles) {            simpleAuthorizationInfo.addRole(role.getKeyword());        }//        调用业务层, 通过用户, 查询该用户拥有的所有权限.同上        List<Permission> permissions = permissionService.findByUser(user);        for (Permission permission : permissions) {            simpleAuthorizationInfo.addStringPermission(permission.getKeyword());        }//        将拥有用户对应的角色以及相应的权限都告诉了Shiro框架之后, 进行返回        return simpleAuthorizationInfo;    }    @Override//    认证身份的方法.即检查输入的用户在数据库表中是否存在    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//       在调用subject.login(token)这个方法时候, 就走到了这里. token就等于这里的AuthenticationToken token参数//        先将这个参数转换成UsernamePasswordToken, 拿到前台传过来的用户名和密码.        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;//        使用shiro查询用户的话, 首先是拿username查询客户信息.        User user = usersService.findByUsername(usernamePasswordToken.getUsername());//        开始校验获取到的User对象, 如果查询后为null ,则表示没有这个User.        if (StringUtils.isEmpty(user)) {//            如果用户名不存在, 则返回一个"简单的认证信息",AuthenticationInfo是一个接口,//              它有一个实现类:SimpleAuthenticationInfo,用于封装简单的认证信息.它可以有三个参数.//          user:期望登陆后保存到subject的信息. null:表示密码为null, getName:表示这个Realm的名称            /*return new SimpleAuthenticationInfo(user, null, getName());*///            如果返回上面的对象, 则当用户为null的时候, 程序会报错://Caused by: java.lang.IllegalArgumentException: principal argument cannot be null.//            接着试着直接返回一个null.则当这个用户名和密码都错的时候报错:// org.apache.shiro.authc.UnknownAccountException: Realm [com.cn.webservice_service.Myrealm@6ce80c48] was unable to find account data for the submitted//            如果用户名是对的, 密码是错的, 则会报另一个错://org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - admin,            return null;        } else {//            这里就是用户不为null, 则返回://            user:将要保存进subject的用户信息.//            user.getPassword(): 这里是通过用户名查找数据库表得到的对应User密码.//            securityManager安全管理器将得到的密码和用户输入的密码进行自动校验.//            getName(): Realm的名称            return new SimpleAuthenticationInfo(user, user.getPassword(), getName());        }//        如果用户名或者密码不存在则会在subject.login(token)这个方法调用的时候会产生用户名或者密码错误异常    }}

其余的业务层以及DAO就跟普通的查询没什么区别.

最重要的点.

是在shiro配置文件中关于对某些资源的权限限制.







原创粉丝点击