文章目录
- 认证流程
- 创建工程
- 1 创建maven工程
- 2. 实现认证功能
- 3. 会话功能
- 4. 授权功能
认证流程
\qquad
基于session的认证方式如下
\qquad
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的session_id存放到cookie中,这样用户客户端请求时带上session_id就可以验证服务器端是否存在session数据,以此完成用户的合法验证,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。
基于Session的认证机制由Servlet规范定制,Servlet容器已实现,用户通过HttpSession的操作方式即可实现,如下是HttpSession相关的操作API。
方法 | 解释 |
---|---|
HttpSession getSession(Boolean create) | 获取当前HttpSession对象 |
void setAttribute(String name,Object value) | 向当前session中存储对象 |
Object getAttribute(String name) | 从session中取对象 |
void removeAttribue(String name) | 移除session中的对象 |
void invalidate() | 使HttpSession失效 |
创建工程
案例使用maven构建,使用SpringMVC,Servlet3.0实现。
本章代码已分享至Gitee: https://gitee.com/lengcz/security-springmvc
1 创建maven工程
结构预览
(1)选择maven(不选择maven模板),创建maven工程
(2)配置pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.it2</groupId>
<artifactId>security-springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
</dependency>
</dependencies>
<build>
<finalName>security-springmvc</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
(3) Spring容器配置
在config包下定义ApplicationConfig.java,它对应web.xml中ContextLoaderListener的配置
/**
* config包下定义ApplicationConfig.java,它对应web.xml中ContextLoaderListener的配置
*/
@Configuration //相当于ApplicationContext.xml
@ComponentScan(basePackages = "com.it2.security.springmvc",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class ApplicationConfig {
//在此配置除了Controller的其它bean,比如:数据库连接池,事务管理器,业务bean等
}
(4) servletContext配置
本案例采用Servlet3.0无web.xml方式,在config包下定WebConfig.java,它对应DispatcherServlet配置
@Configuration //相当于springmvc.xml
@EnableWebMvc
@ComponentScan(basePackages = "com.it2.security.springmvc",
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
//视图解析器
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Autowired
private SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");
}
}
(5)加载Spring容器
在init包下定义Spring容器初始化类SpringApplicationInitializer,此类实现WebApplicationInitializer接口,Spring容器加载启动时加载WebApplicationInitializer接口的所有实现类。
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//spring容器
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ApplicationConfig.class};
}
//servletContext
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
//url-mapping
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
2. 实现认证功能
(1) 在view目录下编写一个登录页面,login.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="utf-8" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="login" method="post">
用户名: <input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
(2) 在WebConfig.java中配置,将根目录指向登录页
//指向登录页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}
(3) 编写登录认证接口
用户进入认证页面后,输入账号和密码,点击登录,请求/login进行身份认证。
- 定义认证接口,此接口用来校验传递过来的用户名、密码校验,若成功则返回该用户的详细信息,否则抛出错误异常:
public interface AuthenticationService {
/**
* 用户认证
*
* @param authenticationRequest
* @return
*/
UserDto authentication(AuthenticationRequest authenticationRequest);
}
认证请求实体和返回实体
/**
* 认证请求参数
*/
@Data
public class AuthenticationRequest {
private String username;
private String password;
}
/**
* 用户信息
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserDto {
public static final String SESSION_USER_KEY = "_user";
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
/**
* 用户权限
*/
private Set<String> authorities;
}
(3) 编写校验实现类
package com.it2.security.springmvc.service;
import com.it2.security.springmvc.model.AuthenticationRequest;
import com.it2.security.springmvc.model.UserDto;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
@Override
public UserDto authentication(AuthenticationRequest authenticationRequest) {
if (null == authenticationRequest || StringUtils.isEmpty(authenticationRequest.getUsername()) ||
StringUtils.isEmpty(authenticationRequest.getPassword())) {
throw new RuntimeException("账号或密码不能为空");
}
UserDto userDto = getUserDto(authenticationRequest.getUsername());
if (null == userDto) {
throw new RuntimeException("用户不存在");
}
if (!authenticationRequest.getPassword().equals(userDto.getPassword())) {
throw new RuntimeException("密码错误");
}
return userDto;
}
public UserDto getUserDto(String username) {
return userMap.get(username);
}
private Map<String, UserDto> userMap = new HashMap<>();
{
Set<String> authorities1 = new HashSet<>();
authorities1.add("p1");
Set<String> authorities2 = new HashSet<>();
authorities2.add("p2");
userMap.put("xiaowang", new UserDto("10", "xiaowang", "123456", "小王", "13800000000", authorities1));
userMap.put("xiaoyu", new UserDto("11", "xiaoyu", "111111", "小鱼", "13900000000", authorities2));
}
}
(4) 定义登录接口
@RestController
public class LoginController {
@Autowired
AuthenticationService authenticationService;
@RequestMapping(value = "/login", produces = "text/plain;charset=utf-8")
public String login(AuthenticationRequest authenticationRequest, HttpSession session) {
UserDto userDto = authenticationService.authentication(authenticationRequest);
session.setAttribute(UserDto.SESSION_USER_KEY, userDto);
return userDto.getUsername() + "登录成功";
}
@RequestMapping(value = "/logout", produces = "text/plain;charset=utf-8")
public String logout(AuthenticationRequest authenticationRequest, HttpSession session) {
session.invalidate();
return "登出成功";
}
}
(5)配置启动
#启动命令
clean tomcat7:run
(6) 启动服务测试
3. 会话功能
在上面,在登录成功会将用户存入session,在访问资源时getSession获取用户身份信息,在登出时,使session 无效已完成会话。
4. 授权功能
(1)首先给用户添加权限
(2)定义拦截器,用于处理权限
//权限拦截
@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//取出用户信息,判断请求的url是否在用户的权限范围内
String requestURI = request.getRequestURI();
Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
if (null == object) {
writeContent(response, "请登录");
return false;
}
UserDto userDto = (UserDto) object;
Set<String> authorities = userDto.getAuthorities();
if (authorities.contains("p1") && requestURI.contains("/r/r1")) {
return true;
}
if (authorities.contains("p2") && requestURI.contains("/r/r2")) {
return true;
}
writeContent(response, "没有权限");
return false;
}
private void writeContent(HttpServletResponse response, String msg) throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print(msg);
writer.close();
}
}
(3)在WebConfig.java注册拦截器
@Autowired
private SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");
}
(4)启动服务测试,登录xiaowang账号,访问资源测试,分别方位r2和r1,可以看到xiaowang在访问r2时没有权限,而可以访问r1