下面来通过一个实例讲解Session认证的方式

创建工程:


引入依赖:

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.0.4.RELEASE</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</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><includes><include>**/*.xml</include></includes></resource></resources></configuration></plugin></plugins></pluginManagement></build>



Spring容器配置

在config包下定义ApplicationConfig.java,这个配置类相当于spring的配置文件

@Configuration

@ComponentScan(basePackages=,excludeFilters={@ComponentScan.Filter(type=FilterType.ANNOTATION,value=Controller.)})ApplicationConfig{}

在config包下定义WebConfig.java,这个配置类相当于springmv的配置文件

@Configuration@EnableWebMvc@ComponentScan(basePackages="cn.xh",includeFilters={@ComponentScan.Filter(type=FilterType.ANNOTATION,value=Controller.class)})publicclassWebConfigimplementsWebMvcConfigurer{//视频解析器@BeanpublicInternalResourceViewResolverviewResolver(){InternalResourceViewResolverviewResolver=newInternalResourceViewResolver();viewResolver.setPrefix("/WEB-INF/view/");viewResolver.setSuffix(".jsp");returnviewResolver;}}

加载spring容器

在init包下定义spring容器的初始化类SpringApplicationInitializer,该类实现了WebApplicationInitializer接口相当于web.xml文件。Spring容器启动时会加载所有实现了WebApplicationInitializer接口的类。

publicclassSpringApplicationInitializerextendsAbstractAnnotationConfigDispatcherServletInitializer{@OverrideprotectedClass<?>[]getRootConfigClasses(){returnnewClass<?>[]{ApplicationConfig.class};}@OverrideprotectedClass<?>[]getServletConfigClasses(){returnnewClass<?>[]{WebConfig.class};}@OverrideprotectedString[]getServletMappings(){returnnewString[]{"/"};}}

该类对应的web.xml文件可以参考:


<web‐app>

<listener>

<listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class>

</listener>

<context‐param>

<param‐name>contextConfigLocation</param‐name>

<param‐value>/WEB‐INF/application‐context.xml</param‐value>

</context‐param>

<servlet>

<servlet‐name>springmvc</servlet‐name>

<servletclass>org.springframework.web.servlet.DispatcherServlet</servlet‐class>

<init‐param>

<param‐name>contextConfigLocation</param‐name>

<param‐value>/WEB‐INF/spring‐mvc.xml</param‐value>

</init‐param>

<load‐on‐startup>1</load‐on‐startup>

</servlet>

<servlet‐mapping>

<servlet‐name>springmvc</servlet‐name>

<url‐pattern>/</url‐pattern>

</servlet‐mapping>

</web‐app>


实现认证功能

在webapp/WEB-INF/views下定义认证页面login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="username"><br>
密&nbsp;&nbsp;&nbsp;码:
<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>

在WebConfig中新增如下配置,将/直接导向login.jsp页面:

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}


启动项目,访问/路径地址,进行测试

创建认证接口;

认证接口用来对传入的用户名和密码进行验证,验证成功返回用户的详细信息,失败抛出错误异常。

public interface AuthenticationService {
/**
*
用户认证
* @param
authenticationRequest 用户认证请求
* @return 认证成功的用户信息
*/
UserDto authentication(AuthenticationRequest authenticationRequest);
}


认证请求结构:

@Data
public class AuthenticationRequest {
/**
*
用户名
*/
private String username;
/**
*
密码
*/
private String password;
}


用户详细信息:

@Data
@AllArgsConstructor
public class UserDto {
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
}


认证实现类:

@Service
public class AuthenticationServiceImpl implements AuthenticationService {
@Override
public UserDto authentication(AuthenticationRequest authenticationRequest) {
if(authenticationRequest == null
|| StringUtils.isEmpty(authenticationRequest.getUsername())
|| StringUtils.isEmpty(authenticationRequest.getPassword())){
throw new RuntimeException("账号或密码为空");
}
UserDto userDto = getUserDto(authenticationRequest.getUsername());
if(userDto == null){
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<>();
{
userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443"));
userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553"));
}
}


登录controller:

@RestController
public class LoginController {
@Autowired
private AuthenticationService authenticationService;
/**
*
用户登录
* @param
authenticationRequest 登录请求
* @return
*/
@PostMapping(value = "/login",produces = "text/plain;charset=utf‐8")

public String login(AuthenticationRequest authenticationRequest){
UserDto userDto = authenticationService.authentication(authenticationRequest);
return userDto.getFullname() + " 登录成功";
}
}

测试



实现会话功能:

当用户登录系统后,系统需要记住用户的信息,一般会把用户的信息放在session中,在需要的时候从session中获取用户的信息,这就是会话机制。

首先在UserDto中定义一个Session_USER_KEY,作为session的key

public static final String SESSION_USER_KEY = "_user";

修改LoginController,认证成功后,将用户的信息放入session,并增加用户注销的方法,用户注销时清空session

@PostMapping(value = "/login",produces = "text/plain;charset=utf‐8")
public String login(AuthenticationRequest authenticationRequest, HttpSession session){
UserDto userDto = authenticationService.authentication(authenticationRequest);
//用户信息存入session
session.setAttribute(UserDto.SESSION_USER_KEY,userDto);
return userDto.getUsername() + "登录成功";
}

@GetMapping(value = "logout",produces = "text/plain;charset=utf‐8")
public String logout(HttpSession session){
session.invalidate();
return "退出成功";
}

增加测试资源,在LoginController中增加测试资源:


@GetMapping(value = "/r/r1",produces = {"text/plain;charset=utf-8"})
public String r1(HttpSession session){
String fullname = null;
Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
if(userObj != null){
fullname = ((UserDto)userObj).getFullname();
}else{
fullname = "匿名";
}
return fullname + " 访问资源1";
}

测试:

未登录访问 /r/r1显示:


已登录访问 /r/r1显示:


实现授权功能

用户访问系统需要经过授权,需要完成如下功能:

禁止未登录用户访问某些资源

登录用户根据用户的权限决定是否能访问某些资源


第一步:在UserDto里增加权限属性表示该登录用户拥有的权限:

@Data
@AllArgsConstructor
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;
}

第二步:在AuthenticationServiceImpl中为用户初始化权限,张三给了p1权限,李四给了p2权限:

//用户信息
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("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authorities1));
userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));

}

第三步:在LoginController中增加测试资源:

/**
*
测试资源2
* @param
session
* @return
*/

@GetMapping(value = "/r/r2",produces = {"text/plain;charset=utf-8"})
public String r2(HttpSession session){
String fullname = null;
Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
if(userObj != null){
fullname = ((UserDto)userObj).getFullname();
}else{
fullname = "匿名";
}
return fullname + " 访问资源2";
}

第四步:在interceptor包下实现授权拦截器SimpleAuthenticationInterceptor:

校验用户是否登录

校验用户是否有操作权限

@Component

public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
//请求拦截方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object
handler) throws Exception {
//读取会话信息
Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
if (object == null) {
writeContent(response, "请登录");
}
UserDto user = (UserDto) object;
//请求的url
String requestURI = request.getRequestURI();
if (user.getAuthorities().contains("p1") && requestURI.contains("/r1")) {
return true;
}
if (user.getAuthorities().contains("p2") && requestURI.contains("/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();
response.resetBuffer();
}
}



在WebConfig中配置拦截器,配置/r/**的资源被拦截器处理:

@Autowired
private SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");
}


测试:

未登录:



张三访问/r/r1:

张三访问 /r/r2: