查询数据库用户信息,实现登录认证。
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.3.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>5.3.1.RELEASE</version> </dependency>
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!--spring容器,是父容器,spring-mvc和spring-security为子容器,导入到父容器中--> <param-value>classpath:applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--编码过滤器,解决乱码问题--> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--过滤器链,对象名称不能改成其他--> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
创建spring-security.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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!--设置不经过SpringSecurity过滤器的静态资源--> <security:http pattern="/css/**" security="none"/> <security:http pattern="/img/**" security="none"/> <security:http pattern="/plugins/**" security="none"/> <security:http pattern="/errors/**" security="none"/> <!--设置可以用el表达式配置,并自动生成对应过滤器--> <security:http auto-config="true" use-expressions="true"> <!--指定可以被匿名访问的页面--> <security:intercept-url pattern="/login.jsp" access="permitAll()"/> <!--使用el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色,才能访问--> <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/> <!--指定自定义的登录页面--> <security:form-login login-page="/login.jsp" login-processing-url="/login" default-target-url="/index.jsp" authentication-failure-url="/errors/failer.jsp"/> <!--指定退出后跳转到哪个页面--> <security:logout logout-url="/logout" logout-success-url="/login.jsp"/> <!--登录页面中记住我功能,开启remember-me过滤器,设置token存储时间为60秒,此处存储在cookie中,也可设置存储在数据库中--> <security:remember-me token-validity-seconds="60"/> </security:http> <!--设置认证用户信息的来源--> <security:authentication-manager> <!--从数据库中查找用户名和密码,user-service-ref指定查询服务对象--> <security:authentication-provider user-service-ref="userService"> <!--下面设置固定用户名和密码--> <!--<security:user-service>--> <!--{noop}指定password不加密--> <!--<security:user name="user" password="{noop}user" authorities="ROLE_USER"/>--> <!--</security:user-service>--> <!--指定加密对象--> <security:password-encoder ref="passwordEncoder"/> </security:authentication-provider> </security:authentication-manager> </beans>
主配置文件applicationContext.xml中引入spring-security.xml,创建加密对象(用于密码加密保存和加密认证):
<import resource="classpath:spring-security.xml"/> <!--加密对象--> <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
userService实现:
@Service("userService") public class UserServiceImp implements UserService { @Resource private UserDao userDao; @Resource private BCryptPasswordEncoder passwordEncoder; public List<User> selectAll() { return userDao.findAll(); } public void addUser(User user) { user.setPassword(passwordEncoder.encode(user.getPassword()));//加密存储 userDao.addUser(user); } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<User> users = userDao.findByUserName(username); if(users == null || users.size() == 0){ throw new UsernameNotFoundException(String.format("JdbcDaoImpl.notFound %s",username)); } User user = users.get(0); List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); for(Role role : user.getRoles()){ grantedAuthorities.add(new SimpleGrantedAuthority(role.getRoleName())); } //密码前拼接"{noop}",表示密码不加密验证,不加,需要加密验证 //本地项目中的User类和spring security中的冲突了,直接带包名创建 return new org.springframework.security.core.userdetails.User(user.getUserName(),user.getPassword(),user.getStatus()==1,true,true,true,grantedAuthorities) ; } public void updateUser(User user) { user.setPassword(passwordEncoder.encode(user.getPassword())); userDao.updateUser(user); } }
要事先准备好用户表、角色表、权限表、用户角色表、角色权限表,并添加一些数据。
--先删除关联表 DROP TABLE IF EXISTS `SYS_ROLE_PERMISSION`; DROP TABLE IF EXISTS `SYS_USER_ROLE`; DROP TABLE IF EXISTS `SYS_USER`; CREATE TABLE `SYS_USER` ( `ID` INT(11) NOT NULL AUTO_INCREMENT, `USER_NAME` VARCHAR(32) NOT NULL COMMENT '用户名称', `PASSWORD` VARCHAR(120) CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI NOT NULL COMMENT '密码', `STATUS` INT(1) DEFAULT '1' COMMENT '1有效,0无效', PRIMARY KEY (`ID`) ) ENGINE=INNODB DEFAULT CHARSET=UTF8; DROP TABLE IF EXISTS `SYS_ROLE`; CREATE TABLE `SYS_ROLE` ( `ID` INT(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `ROLE_NAME` VARCHAR(30) DEFAULT NULL COMMENT '角色名称', `ROLE_DESC` VARCHAR(60) DEFAULT NULL COMMENT '角色描述', PRIMARY KEY (`ID`) ) ENGINE=INNODB DEFAULT CHARSET=UTF8; CREATE TABLE `SYS_USER_ROLE` ( `UID` INT(11) NOT NULL COMMENT '用户编号', `RID` INT(11) NOT NULL COMMENT '角色编号', PRIMARY KEY (`UID`,`RID`), CONSTRAINT `FK_REFERENCE_1` FOREIGN KEY (`RID`) REFERENCES `SYS_ROLE` (`ID`), CONSTRAINT `FK_REFERENCE_2` FOREIGN KEY (`UID`) REFERENCES `SYS_USER` (`ID`) ) ENGINE=INNODB DEFAULT CHARSET=UTF8; DROP TABLE IF EXISTS `SYS_PERMISSION`; CREATE TABLE `SYS_PERMISSION` ( `ID` INT(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `PERMISSION_NAME` VARCHAR(30) DEFAULT NULL COMMENT '菜单名称', `PERMISSION_URL` VARCHAR(100) DEFAULT NULL COMMENT '菜单地址', `PARENT_ID` INT(11) NOT NULL DEFAULT '0' COMMENT '父菜单ID', PRIMARY KEY (`ID`) ) ENGINE=INNODB DEFAULT CHARSET=UTF8; CREATE TABLE `SYS_ROLE_PERMISSION` ( `RID` INT(11) NOT NULL COMMENT '角色编号', `PID` INT(11) NOT NULL COMMENT '权限编号', PRIMARY KEY (`RID`,`PID`), CONSTRAINT `FK_REFERENCE_3` FOREIGN KEY (`RID`) REFERENCES `SYS_ROLE` (`ID`), CONSTRAINT `FK_REFERENCE_4` FOREIGN KEY (`PID`) REFERENCES `SYS_PERMISSION` (`ID`) ) ENGINE=INNODB DEFAULT CHARSET=UTF8;
login.jsp页面中添加标签库,表单标签下添加标签<security:csrfInput/>
<%@taglib prefix="security" uri="http://www.springframework.org/security/tags"%> <form method="post"> <security:csrfInput/> <!--记住我,name属性值必须为remember-me--> <input type="checkbox" name="remember-me" value="true"> </form>
还需要写个login之后的跳转Controller,最后启动tomcat,进行登录验证。
“记住我”功能补充说明:
将用户名密码和token都存在客户端相对不安全,另一种方法是在cookie中仅保存一个无意义的加密串,在数据库中保存加密串与登录用户(用户名、密码、token、时间)的对应关系,下次自动登录时,cookie中的加密串与数据库中的数据一同进行验证。
添加存储表PERSISTENT_LOGINS:
CREATE TABLE `PERSISTENT_LOGINS` ( `SERIES` VARCHAR(64) NOT NULL, `USERNAME` VARCHAR(64) NOT NULL, `TOKEN` VARCHAR(64) NOT NULL, `LAST_USED` TIMESTAMP NOT NULL, PRIMARY KEY (`SERIES`) ) ENGINE=INNODB DEFAULT CHARSET=UTF8;
spring-security.xml中:
<security:remember-me data-source-ref="dataSource" token-validity-seconds="60" />
spring security的核心是过滤器,一系列过滤器组成了过滤器链,如下图:
序号 | 过滤器 | 说明 |
---|---|---|
1 | SecurityContextPersistenceFilter | 使用SecurityContextRepository在session中保存或更新一个 SecurityContext,并将SecurityContext给过滤器链中之后的过滤器使用。 SecurityContext中存储了当前用户的认证以及权限信息。 |
2 | WebAsyncManagerIntegrationFilter | 用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager。 |
3 | HeaderWriterFilter | 向请求的Header中添加信息,可在<security:http>标签内部使用<security:headers>来控制 |
4 | CsrfFilter | 跨域请求伪造过滤,对所有post请求验证是否包含系统生成的csrf的token信息, 如果不包含,则报错。可防止csrf攻击。<security:csrf>可设置禁用,如果为开启,则必须为post请求。 |
5 | LogoutFilter | 匹配URL为/logout的请求,实现用户退出,清除认证信息。 |
6 | UsernamePasswordAuthenticationFilter | 主要负责认证的过滤器,默认匹配URL为/login且必须为POST请求。 |
7 | DefaultLoginPageGeneratingFilter | 如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。 |
8 | DefaultLogoutPageGeneratingFilter | 生成一个默认的退出登录页面 |
9 | BasicAuthenticationFilter | 自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。 |
10 | RequestCacheAwareFilter | 通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest |
11 | SecurityContextHolderAwareRequestFilter | 针对ServletRequest进行了一次包装,使得request具有更加丰富的API |
12 | AnonymousAuthenticationFilter | 当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。 |
13 | SessionManagementFilter | SecurityContextRepository限制同一用户开启多个会话的数量 |
14 | ExceptionTranslationFilter | 异常转换过滤器,用来转换整个链中出现的异常 |
15 | FilterSecurityInterceptor | 获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来判断其是否有权限。 |
标签 | 说明 |
---|---|
<security:authorize access=“hasAnyRole(‘ROLE_ADMIN’)”> | 页面中某些内容需要根据角色显示时,用<security:authorize>包裹 |
<security:authentication property=“principal.username”/> | 显示用户名,property属性值也可以为name |
通过注解的方式来控制类或者方法的访问权限。
application.xml或者spring-mvc.xml中添加配置:
<!--开启security,jsr250,spring注解支持--> <security:global-method-security secured-annotations="enabled" jsr250-annotations="enabled" pre-post-annotations="enabled"/>
Controller或者Service类或者方法上添加注解:
注解 | 说明 |
---|---|
@Secured({“ROLE_ADMIN”,“ROLE_USER”}) | security的注解,有ROLE_ADMIN或ROLE_USER角色才能访问 |
@RolesAllowed({“ROLE_ADMIN”,“ROLE_USER”}) | jsr250的注解 |
@PreAuthorize(“hasAnyRole(‘ROLE_ADMIN’,‘ROLE_USER’)”) | spring的注解 |
禁止访问时,友好页面展示的方式:
spring-security.xml中<security:http>下添加:
<security:access-denied-handler error-page="/errors/403.jsp"/>
web.xml中添加:
<error-page> <error-code>403</error-code> <location>/errors/403.jsp</location> </error-page>
@ControllerAdvice public class ControllerException { @ExceptionHandler(AccessDeniedException.class) public String exception403Advice(){ return"redirect:/errors/403.jsp"; } }