親バカエンジニアのナレッジ帳

webのエンジニアをやっており、日頃の開発で詰まったことや書き残しておきたいことを載せています。育児のイロハという育児サイト(https://ikujip.jp)の開発も行っているため、その開発で使用されている技術についても掲載しています。

Spring Securityを使ったログイン機能 (2)ユーザ情報登録フォームの実装

ti-tomo-knowledge.hatenablog.com

にて、ログイン認証されていない時にログイン画面に遷移される処理の説明をしましたので、
次にログイン画面の実装...に行きたいのですがその前に、
そもそも認証先のデータを用意する必要がありますよね。
そこで今回はユーザデータを登録する処理を実装しようと思います。
いわゆるサインアップですね。

認証先のテーブルについてですが、accountsテーブルというものを用意しました。

create table accounts (
    id integer primary key,
    mail_address varchar(255),
    password char(60),
    updated_at timestamp not null default current_timestamp,
    created_at timestamp not null default current_timestamp
);

登録するデータとしてはメールアドレスとパスワードですね。
もちろんパスワードはハッシュ化した値を入れることになります。

後ほど出てくる話題になりますが、
パスワードはBCrypt(Blowfish暗号)という暗号化を行って保存をします。
暗号化をすると文字数は60文字になるので定義はchar(60)としています。

まずはコントローラについてです。

package test.package;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
@Controller
public class accountController {
 
    @ModelAttribute
    public AccountForm setupForm() {
        return new AccountForm();
    }
 
    @RequestMapping(value="account")
    String accountForm() {
        return "account/accountForm";
    }
}

ポイントは@ModelAttributeアノテーションをつけているsetupForm()メソッドの箇所ですね。
AccountFormは後ほど詳細は記載しますが、formからPOSTされた時に値を受け取るクラスになります。
@RequestMappingでformのあるthymeleafにアクセスする場合、
事前にsetupForm()でインスタンス化をしておけば、
AccountFormで設定したフィールドをformで利用することができるのです。

AccountFormの内容は以下になります。

package test.account;
 
import lombok.Data;
 
import java.io.Serializable;
 
@Data
public class AccountForm implements Serializable {
    private String email;
    private String password;
}

java.io.Serializableインタフェースを実装してシリアライズさせておりますが、
メソッドや定数をもたないインタフェースであるため、オーバーライドするメソッドはありません。
インスタンス変数がシリアライズの対象になるのですが、今回は2つのフィールドが対象となります。
それぞれgetter/setterを設置しなければエラーが発生してしまいますが、
@Dataを見ていただければわかると思いますが、私は楽をするためにLombokを使って記載を省略しています。
今回はサインアップ周りが主題なので、バリデーションなどは一旦省略します。
(もちろん後からちゃんと入れますよw)

accountFormのthymeleafの中身は以下になります。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>ユーザー登録</title>
</head>
<body>
<div>
 
    <form th:action="@{/account}" action="/account" th:object="${accountForm}" method="post">
 
        <h1>ユーザー登録</h1>
 
        <div th:classappend="${#fields.hasErrors('email')}? 'has-error'">
            <label for="email">E-mail</label>
 
            <div>
                <input id="email" type="email" th:field="*{email}" name="email"/>
            <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}">error!</span>
            </div>
        </div>
        <div th:classappend="${#fields.hasErrors('password')}? 'has-error'">
            <label for="password">パスワード</label>
 
            <div>
                <input id="password" type="password" th:field="*{password}" name="password"/>
            <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}">error!</span>
            </div>
        </div>
        <input type="submit" value="新規登録"/>
    </form>
</div>
</body>
</html>

の部分については、
htmlにthymeleafの名前空間を与えているのです。
これで「th:〜」の記述をするとプログラムで使えるようになります。


localhost:8080/account」にアクセスすると、accountFormが開くようになっています。

f:id:tomotomo1129:20180611205532p:plain

後はsubmitした後の登録処理ですが、
事前にModelとサービスクラス、また、リポジトリクラスを定義してください。

まずはModelです。

package test.model;
 
import lombok.Data;
 
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
 
@Entity
@Table(name = "accounts")
@Data
public class Account implements Serializable {
    @Id
    @GeneratedValue
    private Integer id;
 
    @Column(nullable=false)
    private String email;
 
    @Column(nullable=false, length=20)
    private String password;
 
    @Column(nullable=false, updatable=false)
    private Date created_at;
 
    @Column(nullable=false)
    private Date updated_at;
}

ここでもsetter/getterの定義を省略するためにlombakの@Dataを使います。
「@GeneratedValue」はidにつけておけば一意の値が自動で付与されます。

続いてリポジトリです。

package test.repository;
 
 
import org.springframework.data.jpa.repository.JpaRepository;
import test.model.Account;
 
public interface AccountRepository extends JpaRepository<Account, Integer> {
}

これだけでいいのです。
JPAを使ったリポジトリは、JpaRepositoryを継承して対象となるモデルをimportしておけばfineOne、findAll、save、deleteを自動で使えるようになります。

続いてサービスクラスですが、その前に...

Spring Securityを利用するにあたり、
「WebSecurityConfigurerAdapter」を継承して認証設定をしているクラスがあると思いますが、
その中に以下を追加してください。

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

これでパスワードのハッシュ化でBCryptを使うことができます。

さあ、改めてサービスクラスです。

package test.service;
 
import test.model.Account;
import test.repository.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
@Transactional
public class AccountService {
    @Autowired
    AccountRepository accountRepository;
    @Autowired
    PasswordEncoder passwordEncoder;
 
    public Account create(Account account, String rawPassword) {
        String encodedPassword = passwordEncoder.encode(rawPassword);
        account.setPassword(encodedPassword);
        return accountRepository.save(account);
    }
}

リポジトリをimportし、保存処理を書いています。
また、パスワードはそのまま保存するわけにはいかないので、ハッシュ化するための関数passwordEncoderも使っています。
「PasswordEncoder passwordEncoder;」において、先ほど定義したBCryptPasswordEncoderのインスタンス化が行われています。

次にコントローラにPOSTされた時の処理を記載します。

@RequestMapping(value = "account", method = RequestMethod.POST)
String create(@Validated AccountForm form, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "account/accountForm";
    }
    Account account = new Account();
    account.setEmail(form.getEmail());
    accountService.create(account, form.getPassword());
    return "redirect:/acount/complete";
}
 
@RequestMapping(value = "account/complete", method = RequestMethod.GET)
String createFinish() {
    return "account/accountComplete";
}

もちろんaccountServiceをインスタンス化してからこのメソッドを定義してください。
以上でフォームにメールアドレスとパスワードを入力してPOSTすれば、
無事にデータが登録されます。

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Spring Boot + ThymeleafのCSRFトークン設定は超簡単

Spring Boot + ThymeleafにおけるCSRF対策

近頃のWebアプリケーションフレームワークにおけるCSRF対策は自動で簡単にできるものが多いですが、Spring BootにおけるCSRF対策でのformタグへのトークン付与もとても簡単です。
「spring-boot-starter-security」というライブラリを使い、formタグに対して、「th:action=〜」の記述さえすれば自動的に付与されるのです。
では実際にやってみましょう。

spring-boot-starter-securityライブラリの追加

pom.xmlのdependenciesの中に以下を追加してください。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

@EnableWebSecurityの追加

WebSecurityConfigurerAdapterを継承したクラスに、@EnableWebSecurityのアノテーションを追加しておきます。

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

formの作成

あとはHTMLで、以下のようなformを作成し、th:actionの記述もしてください。

<form th:action="@{/testPost}" action="/testPost" method="post">
 ・
 ・
 ・
</form>

実際に表示を見てみると、

<form action="/testPost" method="post">
  <input type="hidden" name="_csrf" value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
</form>

のようなタグが入っているかと思います。

最近のフレームワークはやっぱり便利ですね〜。

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Spring BootでFlywayを使ったマイグレーション

Spring Bootのマイグレーション

開発をするにあたってDBのバージョン管理をしっかりするためにもマイグレーションは必要です。
Spring Bootでは、Flywayを使えばとても楽にマイグレーションを行うことができます。

※この手順では、application.ymlなどに以下のような設定があり、データベースとの接続設定は完了しているものとします。

例)

spring:
  datasource:
    url: jdbc:mysql://localhost/test-database?useSSL=false&characterEncoding=UTF-8
    username: root
    password: root
    driverClassName: com.mysql.jdbc.Driver

Flywayライブラリの追加

まず、Flywayライブラリを使うためにpom.xmlに以下を追加してください。

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

もちろんMavenの再読み込みは忘れずに。

マイグレーションパッケージ、ファイルの作成

次に、src/main/resourcesにdb.migrationパッケージを作成してください。

これで準備OKです。
めちゃめちゃ簡単ですね。

マイグレーションファイルは、「V + バージョン番号__任意のファイル名.sql」といった形式で作成してください。
例えば以下のようになります。
V1__create-users.sql
V2__create-products.sql
V3__alter-users.sql

任意のファイル名の箇所は本当に何でも良いですが、後で見た時に意味がわかるようなファイル名にしておきましょう。
マイグレーションファイルは、開発を重ねるほどどんどん増えていくため、内容によってファイル名のルールを決めておくと良いでしょう。

例としてUsersテーブルを作成するマイグレーションファイルを作成します。

V1__create-users.sql

create table users (
    id integer primary key,
    mail_address varchar(255),
    password char(30),
    updated_at timestamp not null default current_timestamp,
    created_at timestamp not null default current_timestamp
);

通常のSQLDDL文ですよね。
この状態でアプリケーションを起動させると、usersテーブルが作成されます。

バージョン情報はschema_versionというテーブルで履歴が管理され、アプリケーション実行時に未反映のマイグレーション処理が実行されます。
初回の実行時にはschema_versionテーブルは存在していないはずなので、そのような場合は勝手にテーブルは作成されるので心配いりません。

マイグレートに失敗した場合

例えばシンタックスエラーなど、SQLが失敗する要因はいくつかあると思いますが、失敗した場合、scheme_versionテーブルの該当のバージョンのsuccessカラムの値が0のままになります。(成功した場合は1になります。)

この状態でSQLを修正して再実行しても上手くいきません。
再度実行したい場合は、該当のバージョンのレコードを削除してから再実行しましょう。
失敗時の再実行に関しては少し面倒ですね。

Fire TV Stick

Fire TV Stick

新登場  Fire TV Stick 4K - Alexa対応音声認識リモコン付属

新登場 Fire TV Stick 4K - Alexa対応音声認識リモコン付属

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Fire HD 10 タブレット (10インチHDディスプレイ) 64GB

Spring BootでJPAを使用したデータベース設定

Spring BootでJPAの設定

Spring Bootでデータベースを利用するために、JPAの設定をしようと思います。
そもそもJPAとは「Java Persistence API(Javaの永続化のAPI)」の略であり、Javaの純正技術として浸透しているORMの仕様です。
なので、実際にはORMの技術で実装されたライブラリ(いわゆるプロバイダ)を使用します。
有名なライブラリとしてはHibernateなどがありますね。
Spring Bootでは、Hibernateの技術を駆使したライブラリとしてSpring Data JPAが用意されており、今回はこれを設定します。

Spring Data JPAのライブラリのインストール

まずはpom.xmlに以下を追加してSpring Data JPAのライブラリをインストールしましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

また、JavaアプリケーションからMySQLに接続するために、
JDBCドライバのインストールもしましょう。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

scopeのruntimeは、実行時のみに必要な場合の指定で、JDBCドライバの場合はruntimeを使うことが普通です。
あまり深く考えず、上記のように設定してください。

アプリケーションの起動

上記の状態でアプリケーションを起動させてください。

そうすると、以下のようなエラーが発生します。

***************************
APPLICATION FAILED TO START
***************************
 
Description:
 
Cannot determine embedded database driver class for database type NONE
 
Action:
 
If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
 
 
Process finished with exit code 1

DB接続のライブラリはあるが、設定情報が書かれていないために、
どこに接続したら良いのかわからないと言っていますね。

DB接続の設定は、src/main/resources以下にapplication.ymlか、または、application.propertiesを作成します。

application.ymlに記載する場合は以下のようになります。

spring:
  datasource:
    url: jdbc:mysql://localhost/test_database?useSSL=false
    username: 環境ごとのユーザ名
    password: 環境ごとのパスワード
    driverClassName: com.mysql.jdbc.Driver

urlの末尾にある「?useSSL=false」はSSL接続をするか否かの設定ですが、
SSL接続をしない場合は基本的に指定は不要ですが、
実行時にWARNINGのメッセージが大量に出て不気味なのでfalseの指定をすることにしました。

これでアプリケーションを起動し、エラーなどが発生しなければひとまず設定はOKです。

ちなみにapplication.propertiesに設定する場合は、以下のように記載してください。

spring.datasource.url=jdbc:postgresql://localhost:3306/test_database
spring.datasource.username=環境ごとのユーザ名
spring.datasource.password=環境ごとのパスワード
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

まとめ

上記の手順でSpring Boot JPAの設定は完了しました。
あくまでローカル環境の開発用の設定になるので、本番環境なので使う場合には、application-production.ymlやapplication-production.propertiesを作成する必要があります。
それはまた別な機会で。。。

Spring Boot in Action

Spring Boot in Action

Spring Securityを使ったログイン機能 (1)未ログイン時の画面遷移

Spring BootのSpring security

Spring BootにはSpring securityという認証と認可を司るコンポーネントがありますが、これを使いながら数回に分けてログイン機能を実装したいと思います。
未ログイン時にログインフォームに遷移する動きから、最終的にはデータベースのユーザデータと認証をするところまで実装したいと思います。

ログイン画面の実装

まずはApplication起動用のパッケージを用意します。
Spring Bootでは何をするにもSpringApplication.runで起動しなければ始まりませんからね。
僕はsrc/main/java/パッケージ直下にApplication.javaというファイル名で配置しました。

package test.package;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

次にログインフォームを設置するので(今回は単純にHello Worldを表示するだけですが...)、
template/login直下にloginForm.htmlというテンプレートを用意します。

コントローラーはsrc/main/java/app/loginというパッケージを用意して、
LoginController.javaを作りました。

package test.package;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
@Controller
@RequestMapping("login")
public class LoginController {
 
    @RequestMapping(value="")
    String loginForm() {
        return "login/loginForm";
    }
}

まずはこの時点でコンパイルし、localhost:8080/loginでログイン画面が表示されるか確認しましょう。
(何度も書きますが、今回は単純にHello Worldを表示するだけですが...w)、

f:id:tomotomo1129:20180609200926j:plain

ログインしていない時は閲覧不可に

さて、今普通に見れていたこのページをログインしていない状態では閲覧できないようにします。
ログインしていないのであればログインフォームは見れるようにしなければいけないですがwあくまで実験用です。
後から例外的にログインページだけ見れるようにします。

Spring Securityを使うには、spring-boot-starter-securityが必要なため、
pom.xmlに以下を追加してインポートします。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

続いて、WebSecurityConfigurerAdapterクラスを継承したクラスを作り、
各種必要な設定をしましょう。

まずはクラスの作成です。

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
 
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
}

こちらは設定クラスの作成なので@Configurationを付与します。
(後に@Beanアノテーションを付与したメソッドも定義するので、その意味でも@Configurationは必要です。)
「@EnableWebSecurity」はThymeleafを共に使うことで、formの中にCSRFトークンが自動で埋め込まれます。
まだformは作りませんが、後で忘れないようにここで入れておきましょう。

※@Configurationとともに@EnableWebMvcSecurityのアノテーションを貼るソースコードをいくつか見かけましたが、
僕が使った最新のSpring Bootのバージョン(1.3.1.RELEASE)では非推奨になったようです。

次に例外となるディレクトリやファイルを設定します。
下記のようなディレクトリやファイルについては、ログインをしていようがしていまいがアクセスできるようにします。

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/favicon.ico", "/css/**", "/js/**", "/images/**", "/fonts/**");
}

次は認証が必要となるURLページの設定です。
以下の設定は、
「http.authorizeRequests()」は認証が必要となるURLを設定する関数で、
「antMatchers("〜").permitAll()」は認証が不要の例外ページ、
「anyRequest().authenticated();」で、それ以外のページは認証された状態でいる必要がある、ということになります。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests() // 認証が必要となるURLを設定します
        .antMatchers("/account/**").permitAll() // /account以下のURLも認証不要
        .anyRequest().authenticated(); // それ以外はすべて認証された状態じゃなきゃダメだよ〜
}

一旦この時点でプログラムを動かしてみましょう。
ログイン認証なしでアクセスが許されているのは、静的ファイルと/account/以下のURLのみです。
よって、当初は通っていたloginが通らなくなっているはずです。

コンパイルしてlocalhost/loginにアクセスすると...

f:id:tomotomo1129:20180609201324j:plain

ということでアクセスできなくなりました。
想定通りですね。

ログインフォームはログインしていなくても閲覧可能に

通常ログインフォームの画面に行く際は当然ログイン状態である必要はないので、認証不要のページにログインフォームも追加します。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests() // 認証が必要となるURLを設定します
        // ↓追加
        .antMatchers("/login").permitAll() // /loginFormは認証不要
        // ↑追加
        .antMatchers("/account/**").permitAll() // /account以下のURLも認証不要
        .anyRequest().authenticated(); // それ以外はすべて認証された状態じゃなきゃダメだよ〜
}

これでアクセスすれば再度問題なくログインフォームの画面は開きます。

ログインしていない場合はログインフォームに飛ばす

さて、最後にログインしていない場合にログインが必要なページに遷移した場合の挙動です。
以下のようにformLogin()で繋げて以下のようにURLを入力すれば、ログインしていない場合の挙動を設定することが可能になります。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/login").permitAll()
        .antMatchers("/account/**").permitAll()
        .anyRequest().authenticated()
    .and()
        .formLogin() // ログインページに飛ばすよ
        .loginProcessingUrl("/login") // ログイン処理をするURL
        .loginPage("/login"); // ログインページのURL

これで未ログイン時の挙動はできましたね。
以下のページでは、ログインフォームを本格的に作成する手順について掲載しています。

ti-tomo-knowledge.hatenablog.com

こちらも参考にしてみてください。

Spring Boot 2 プログラミング入門

Spring Boot 2 プログラミング入門

Spring Boot in Action

Spring Boot in Action

Spring BootにおけるBeanクラスとは

JavaにおけるBean

まずはJavaにおけるBeanをご存知でしょうか。
BeanとはJavaBeansの略であり、インスタンス化して使用するクラスです。

変数のアクセス修飾子はprivateとし、その変数の値を変更したり、取得する際にはpublicなメソッドを使用します。
また、引き数なしのコンストラクタを持ちます。
いわゆるゲッター(getter)やセッター(setter)を保持するクラスですね。

JavaBeansを使うことについては賛否がありますが、
私自身はソースコードの共通化や可読性のためにも利用するのは好きですね。

Spring FrameworkにおけるBean

Spring Bootにおける使用方法の前に、Spring FrameworkにおけるBeanの使い方ですが、使用するBeanの定義をXMLファイルに宣言する必要があるのです。
以下のような感じで毎回。。。

<bean id="Beanの名前" class="パッケージ + クラス名">

詳細な設定方法については、ググればたくさん詳しいものが出てくると思うのでここでは割愛します。
とにかくインスタンス化したいクラスを作成するたびにXMLファイルを編集する必要があり、面倒くさいんですね。
Spring FrameworkXML地獄で面倒だという理由がよくわかります。

Spring BootでBeanの管理をもっと簡単に

そこで、Spring Bootです。
Spring Bootではアノテーション方式で設定が可能なのです。

まず、Beanインスタンス作成するためのメソッドの前に@Beanアノテーションを追加し、Beanインスタンスを返値として指定します。

また、そのメソッドを保持するクラスの前には@Configurationアノテーションをつけてください。
これで設定ファイル(XMLファイル)を使わずにBean設定をするクラスと認識させることができるのです。

package パッケージ;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class TestBeanConfig {
 
    @Bean
    public TestBeanClass testBean() {
        return new Beanクラス; // Beanインスタンスを返す
    }
 
}

ちなみに、@Component、@Service、@Repository、@Controllerをつけるクラスについては、このアノテーションをつけた時点でBeanとして登録されるため、@Beanを付与する必要はありません。

Spring Boot 2 プログラミング入門

Spring Boot 2 プログラミング入門

特定のポートを使用しているプロセスを確認して強制終了させる

プロセスが起動中なのにPIDがわからないから削除できない

プロセスで困ったこととして、例えば以下のことがありませんか?

  • IDEでアプリケーションを起動して開発をしていたが、IDEが途中で落ちてしまった。アプリケーションを終了していないのでプロセスが起動中だが、プロセス(PID)がわからない
  • サーバ上でアプリを起動させようとした時に、「そのポートは使用中だよ」的なメッセージが出て起動できない

プログラミングをよくやられる方ならどちらも一度は経験しているのではないでしょうか。
いずれにせよ、このような場合はポートを使用しているプロセスを確認して強制終了させるしかありません。

ポート番号からプロセスを確認

使いたいポート番号のプロセスを確認したい時は以下でOK!
※ポートは例として8080にしています。「-i」はネットワークソケットファイルの表示という意味ですが、ちょっと難しいのであまり意味を考えなくてもいいのではないでしょうか。

lsof -i:8080

すると、以下のように使用中のプロセスを確認できます。

killを使ってプロセスを強制終了

あとはkillを使えばそのプロセスを終了させられます。
今回で言えば、上記キャプチャのPIDに当たる部分、つまり49137を指定します。

kill -9 49137

これでOKです!
とても簡単な手順ですよね。

新しいLinuxの教科書

新しいLinuxの教科書

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

日経Linux 2018年 7 月号

日経Linux 2018年 7 月号

入門者のLinux 素朴な疑問を解消しながら学ぶ (ブルーバックス)

入門者のLinux 素朴な疑問を解消しながら学ぶ (ブルーバックス)