【相关已翻译的本系列其他文章,点击分类里面的spring security 4】
【 翻译by 明明如月 QQ 605283073】
上一篇文章:Spring Security 4 基于角色的登录例子(带源码)
下一篇文章:Spring Security 4 整合Hibernate Bcrypt密码加密(带源码)
原文地址: http://websystique.com/spring-security/spring-security-4-hibernate-annotation-example/
本教程通过注解和xml两种方式在Spring 4 MVC应用中,整合 Hibernate和Spring Security 4实现权限验证。
前面的文章都是in-memory authentication(基于内存的权限验证) 。
但是在实际的项目中证书都保存在数据库中或者LDAP中。本文将实现一个完整的 Spring security 和Hibernate整合实现权限验证。
仅仅是验证方法本身发生了变化。
下面是以前的例子:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("root123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
}
...
...
将被下面的 database authentication(基于数据库的校验)所替换
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
所有的证书都存在数据库中,将通过 Spring Security 中的
org.springframework.security.core.userdetails.UserDetailsService类实访问控制。
我们提供一个UserDetailsService实现,使用userService 来实现数据库访问。
其他的配置参考以前的文章。
下面是完整的实例代码。我们实现了分层方便管理。
-----------------------
涉及的技术和软件:
让我开始吧
下面是最终的项目目录结构
现在让我为你展示上面目录结构里面的内容和每个的详细介绍。
第2步: 更新 pom.xml 包含所需的依赖
4.0.0
com.websystique.springsecurity
SpringSecurityHibernateAnnotationExample
1.0.0
war
SpringSecurityHibernateAnnotationExample
4.1.6.RELEASE
4.0.1.RELEASE
4.3.6.Final
5.1.31
org.springframework
spring-core
${springframework.version}
org.springframework
spring-web
${springframework.version}
org.springframework
spring-webmvc
${springframework.version}
org.springframework
spring-tx
${springframework.version}
org.springframework
spring-orm
${springframework.version}
org.springframework.security
spring-security-web
${springsecurity.version}
org.springframework.security
spring-security-config
${springsecurity.version}
org.hibernate
hibernate-core
${hibernate.version}
mysql
mysql-connector-java
${mysql.connector.version}
javax.servlet
javax.servlet-api
3.1.0
javax.servlet.jsp
javax.servlet.jsp-api
2.3.1
javax.servlet
jstl
1.2
org.apache.maven.plugins
maven-compiler-plugin
3.2
1.7
1.7
org.apache.maven.plugins
maven-war-plugin
2.4
src/main/webapp
SpringSecurityHibernateAnnotationExample
false
SpringSecurityHibernateAnnotationExample
------------------------------------------
添加spring security到我们应用中第一步是要创建Spring Security Java 配置类。
这个配置创建一个叫springSecurityFilterChain的Servlet过滤器,来对我们应用中所有的安全相关的事项(保护应用的所有url,验证用户名密码,表单重定向等)负责。
package com.websystique.springsecurity.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin().loginPage("/login")
.usernameParameter("ssoId").passwordParameter("password")
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
正如前面提到的最为的变化是:由基于内存的权限验证变为 database authentication(基于数据库的校验)。
上面的配置对应的xml配置如下:
下面是定制初始化war包中的 springSecurityFilter(第三步中的) 注册类
package com.websystique.springsecurity.configuration;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
上面配置对应的xml配置如下:
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
此service为 Authentication Manager(验证管理器)提供验证详情。
它实现了Spring的UserDetailsService接口只包含loadUserByUsername一个方法。
获取用户名[本例中是ssoId 然后返回一个 org.springframework.security.core.userdetails.User对象
我们将在UserService 中通过UserDao对象来获取。
package com.websystique.springsecurity.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.websystique.springsecurity.model.User;
import com.websystique.springsecurity.model.UserProfile;
@Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
@Autowired
private UserService userService;
@Transactional(readOnly=true)
public UserDetails loadUserByUsername(String ssoId)
throws UsernameNotFoundException {
User user = userService.findBySso(ssoId);
System.out.println("User : "+user);
if(user==null){
System.out.println("User not found");
throw new UsernameNotFoundException("Username not found");
}
return new org.springframework.security.core.userdetails.User(user.getSsoId(), user.getPassword(),
user.getState().equals("Active"), true, true, true, getGrantedAuthorities(user));
}
private List getGrantedAuthorities(User user){
List authorities = new ArrayList();
for(UserProfile userProfile : user.getUserProfiles()){
System.out.println("UserProfile : "+userProfile);
authorities.add(new SimpleGrantedAuthority("ROLE_"+userProfile.getType()));
}
System.out.print("authorities :"+authorities);
return authorities;
}
}
------------------------------------------------------
package com.websystique.springsecurity.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HelloWorldController {
@RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET)
public String homePage(ModelMap model) {
model.addAttribute("greeting", "Hi, Welcome to mysite");
return "welcome";
}
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "admin";
}
@RequestMapping(value = "/db", method = RequestMethod.GET)
public String dbaPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "dba";
}
@RequestMapping(value = "/Access_Denied", method = RequestMethod.GET)
public String accessDeniedPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "accessDenied";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage() {
return "login";
}
@RequestMapping(value="/logout", method = RequestMethod.GET)
public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login?logout";
}
private String getPrincipal(){
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
userName = ((UserDetails)principal).getUsername();
} else {
userName = principal.toString();
}
return userName;
}
}
package com.websystique.springsecurity.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.websystique.springsecurity")
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {
@Bean(name="HelloWorld")
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
/*
* Configure ResourceHandlers to serve static resources like CSS/ Javascript etc...
*
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
}
---------译者增加 start---明明如月-------- 以上配置对应的xml配置如下:
/static/**" location="/static/" />
---------译者增加end---明明如月--------
package com.websystique.springsecurity.configuration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class[] getRootConfigClasses() {
return new Class[] { HelloWorldConfiguration.class };
}
@Override
protected Class[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
-------------------------------------------------------------------------
一个用户可以有多种角色 [DBA,ADMIN,USER],一个角色也可以被分配给多个用户。
因此在UserProfile[role] 和User之间,这里有Many-To-Many 关系映射。
我们保持的是单向映射即[User 对UserProfile],
因为我们主要关心通过给定的用户查找角色。
package com.websystique.springsecurity.model;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name="APP_USER")
public class User {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
@Column(name="SSO_ID", unique=true, nullable=false)
private String ssoId;
@Column(name="PASSWORD", nullable=false)
private String password;
@Column(name="FIRST_NAME", nullable=false)
private String firstName;
@Column(name="LAST_NAME", nullable=false)
private String lastName;
@Column(name="EMAIL", nullable=false)
private String email;
@Column(name="STATE", nullable=false)
private String state=State.ACTIVE.getState();
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "APP_USER_USER_PROFILE",
joinColumns = { @JoinColumn(name = "USER_ID") },
inverseJoinColumns = { @JoinColumn(name = "USER_PROFILE_ID") })
private Set userProfiles = new HashSet();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSsoId() {
return ssoId;
}
public void setSsoId(String ssoId) {
this.ssoId = ssoId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Set getUserProfiles() {
return userProfiles;
}
public void setUserProfiles(Set userProfiles) {
this.userProfiles = userProfiles;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((ssoId == null) ? 0 : ssoId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof User))
return false;
User other = (User) obj;
if (id != other.id)
return false;
if (ssoId == null) {
if (other.ssoId != null)
return false;
} else if (!ssoId.equals(other.ssoId))
return false;
return true;
}
@Override
public String toString() {
return "User [id=" + id + ", ssoId=" + ssoId + ", password=" + password
+ ", firstName=" + firstName + ", lastName=" + lastName
+ ", email=" + email + ", state=" + state + ", userProfiles=" + userProfiles +"]";
}
}
package com.websystique.springsecurity.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="USER_PROFILE")
public class UserProfile {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
@Column(name="TYPE", length=15, unique=true, nullable=false)
private String type = UserProfileType.USER.getUserProfileType();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof UserProfile))
return false;
UserProfile other = (UserProfile) obj;
if (id != other.id)
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
@Override
public String toString() {
return "UserProfile [id=" + id + ", type=" + type + "]";
}
}
package com.websystique.springsecurity.model;
public enum UserProfileType {
USER("USER"),
DBA("DBA"),
ADMIN("ADMIN");
String userProfileType;
private UserProfileType(String userProfileType){
this.userProfileType = userProfileType;
}
public String getUserProfileType(){
return userProfileType;
}
}
package com.websystique.springsecurity.model;
public enum State {
ACTIVE("Active"),
INACTIVE("Inactive"),
DELETED("Deleted"),
LOCKED("Locked");
private String state;
private State(final String state){
this.state = state;
}
public String getState(){
return this.state;
}
@Override
public String toString(){
return this.state;
}
public String getName(){
return this.name();
}
}
package com.websystique.springsecurity.dao;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AbstractDao {
private final Class persistentClass;
@SuppressWarnings("unchecked")
public AbstractDao(){
this.persistentClass =(Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
@Autowired
private SessionFactory sessionFactory;
protected Session getSession(){
return sessionFactory.getCurrentSession();
}
@SuppressWarnings("unchecked")
public T getByKey(PK key) {
return (T) getSession().get(persistentClass, key);
}
public void persist(T entity) {
getSession().persist(entity);
}
public void delete(T entity) {
getSession().delete(entity);
}
protected Criteria createEntityCriteria(){
return getSession().createCriteria(persistentClass);
}
}
package com.websystique.springsecurity.dao;
import com.websystique.springsecurity.model.User;
public interface UserDao {
User findById(int id);
User findBySSO(String sso);
}
package com.websystique.springsecurity.dao;
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;
import com.websystique.springsecurity.model.User;
@Repository("userDao")
public class UserDaoImpl extends AbstractDao implements UserDao {
public User findById(int id) {
return getByKey(id);
}
public User findBySSO(String sso) {
Criteria crit = createEntityCriteria();
crit.add(Restrictions.eq("ssoId", sso));
return (User) crit.uniqueResult();
}
}
package com.websystique.springsecurity.service;
import com.websystique.springsecurity.model.User;
public interface UserService {
User findById(int id);
User findBySso(String sso);
}
package com.websystique.springsecurity.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.websystique.springsecurity.dao.UserDao;
import com.websystique.springsecurity.model.User;
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDao dao;
public User findById(int id) {
return dao.findById(id);
}
public User findBySso(String sso) {
return dao.findBySSO(sso);
}
}
-------------------------------------
hibernate配置类添加 @Bean对DataSource配置的方法以及SessionFactory & Transaction Manager上。
数据源属性从application.properties文件获取,其中也包含连接mysql 数据库的链接等。
package com.websystique.springsecurity.configuration;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@ComponentScan({ "com.websystique.springsecurity.configuration" })
@PropertySource(value = { "classpath:application.properties" })
public class HibernateConfiguration {
@Autowired
private Environment environment;
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.websystique.springsecurity.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
application.properties
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/websystique
jdbc.username = myuser
jdbc.password = mypassword
hibernate.dialect = org.hibernate.dialect.MySQLDialect
hibernate.show_sql = true
hibernate.format_sql = true
---------------------------
login.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
Login page
Invalid username and password.
You have been logged out successfully.
注意:和CSRF 相关的是
这一行的目的是防止CSRF攻击。正如你所见jsp中CSRF参数使用EL表达式获取的。因此需要允许el表达式:
需要在jsp头添加如下一行:
<%@ page isELIgnored="false"%>
welcome.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
Welcome page
Greeting : ${greeting}
This is a welcome page.
admin.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
Admin page
Dear ${user}, Welcome to Admin Page.
">Logout
dba.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
DBA page
Dear ${user}, Welcome to DBA Page.
">Logout
accessDenied.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
AccessDenied page
Dear ${user}, You are not authorized to access this page
">Logout
-----------------------------
我们已经创建了 User, UserProfile和关系表
我们填充用户和角色:
Bill,Danny : USER
Sam : ADMIN
Nicole : DBA
Kenny : ADMIN, DBA
此时mysql数据库的截图:
接下来我们启动web应用程序,尝试根据不同用户的不同角色来登录和访问。
现在构建 war 包(通过eclipse或者myeclipse)或者通过maven 命令行( mvn clean install
). 在一个 Servlet 3.0 容器中发布本应用. 在这里我使用的是tomcat, 我将 war 文件放到 tomcat webapps 文件夹然后点击
tomcat安装目录的bin文件夹下的 start.bat
.
启动应用 打开浏览器 在地址栏输入localhost:8080/SpringSecurityHibernateAnnotationExample/并回车
localhost:8080/SpringSecurityHibernateAnnotationExample/admin 访问admin界面
将会被转到登录界面
通过USER角色的账户进行登录
提交表单,将到 访问拒绝页面
通过ADMIN 角色的用户登录
提供一个admin权限的证书并登录
访问db 页面
localhost:8080/SpringSecurityHibernateAnnoationExample/db 将出现访问拒绝
通过DBA角色的账户登记即可
本文结束。下一篇文章将介绍Spring Security 4 整合Hibernate基于角色的登录。
代码下载地址:http://websystique.com/?smd_process_download=1&download_id=1408
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有