ログイン成功後、IDとユーザー名を各フォームで使用したい

タグの編集
投稿者 社会人  (社会人) 投稿日時 2014/6/5 08:55:17
使用ソフト
・Microsoft SQL server 2008 Management Studio
・Microsoft Visual Studio 2010 Professional内のVisual Basic2010

使用PC
・XP SP3

使用コード
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=15554

求めたい情報
・下に記載したコードを使ってログイン成功時のIDとユーザー名を抽出。その後、使用したい時にID、ユーザー名を自由に使える様にしたい。
・ユーザー名は、ログイン成功後に移動するフォームのタイトルLabelにユーザー名を記載させたい。
・IDは、新規でデータを登録する際にSQLserverに誰か登録させたか分かる様に登録させたい。(利用者が操作してIDを反映させる訳では無く、裏作業で登録させたい。)

いつもお世話になっています。
現在、VBとSQLを使って、データの新規登録、変更、検索、削除などのデータベースプログラムを作成している者です。
上記に書いてありますが、SQLのデータベースに「登録ID」という項目があり、ログインに成功したIDがデータを新規登録時に自動的に登録仕組みと、ログイン成功後に「FmMain」というフォームに一旦移動する仕組みになっており、その「FmMain」のLabelに「ユーザー名さんが利用中です」と表現させたいと思っています。
宜しくお願いします。

Imports System.Data.SqlClient

Public Class FmLogin
    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click
        
        If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
            Me.Hide()
            Dim Main As New fmmain
            Main.ShowDialog(Me)
            Me.Show()
            Me.Close()
        Else
            MsgBox("ID,パスワードが一致しません")
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs Boolean
        Dim RESULT As Integer = 0
        Dim SQL As String = "SELECT COUNT(ユーザーID) FROM ユーザー情報" _
                            & " WHERE ユーザーID='" & id & "' AND パスワード='" & pass & "'"

        Using conn As New SqlConnection(ConnectString)
            Dim CMD As New SqlCommand(SQL, conn)
            Try
                conn.Open()
                RESULT = Convert.ToInt32(CMD.ExecuteScalar())
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using

        Return RESULT <> 0
    End Function

    Private Sub btnQuit_Click(sender As System.Object, e As System.EventArgs) Handles btnQuit.Click
        Me.Close()
    End Sub
End Class
投稿者 るきお  (社会人) 投稿日時 2014/6/5 12:51:47
>・下に記載したコードを使ってログイン成功時のIDとユーザー名を抽出。
IDは抽出するまでもなく、TextBox1.Text.Trimですね。
ユーザー名の方はテーブル「ユーザー情報」から取得できると思うので、仮に列名が「ユーザー名」であるとすれば、
SQL文で
SELECT COUNT(ユーザーID) FROM ...

SELECT ユーザー名 FROM ...
にして、
変数RESULTを文字列型にして受け取るようにすれば良いと思います。

>その後、使用したい時にID、ユーザー名を自由に使える様にしたい。
プログラム全体でID、ユーザー名を使用できるようにするためにはいくつかの方法があります。
一例を紹介します。

まず、IDとユーザー名を保存するためのクラスを定義します。次のようにします。
Public Class UserInfo
    Public Shared Property Id As String
    Public Shared Property UserName As String
End Class


IDとユーザー名が確定した段階で上記プロパティに保存します。
CheckUserの中で次のように書くと良いでしょう。
If RESULT IsNot Nothing Then
    UserInfo.id = id
    UserInfo.UserName = RESULT
End If


使用したい箇所で UserInfo.Id または UserInfo.UserName で目的の値が取得できます。

>・ユーザー名は、ログイン成功後に移動するフォームのタイトルLabelにユーザー名を記載させたい。
上記の例のようにIDとユーザー名を保存している場合FormのShownイベントなどで目的のラベルにセットすれば良いと思います。
例:
Label1.Text = UserInfo.UserName


>・IDは、新規でデータを登録する際にSQLserverに誰か登録させたか分かる様に登録させたい。(利用者が操作してIDを反映させる訳では無く、裏作業で登録させたい。)
データを登録するSQLの値に UserInfo.Id を使用すればできます。

それぞれもっと具体的に質問していただければ、より詳細な回答ができるかもしれません。

なお、社会人さんのSQLにはSQLインジェクションの脆弱性があります。
 Dim SQL As String = "SELECT COUNT(ユーザーID) FROM ユーザー情報" _
                             & " WHERE ユーザーID='" & id & "' AND パスワード='" & pass & "'"


たとえば、TextBox1にユーザーが次の値を入力しても大丈夫でしょうか?
' AND 1 = 1 -- 

これを入力されると誰でもログオンできてしまいませんか?


 
投稿者 るきお  (社会人) 投稿日時 2014/6/5 12:53:21
ちょっと訂正します。

最後の危険な入力例は

' AND 1 = 1 --

ではなく、

' OR 1 = 1 --

です。

他にもいろいろなパターンがありそうです。
投稿者 社会人  (社会人) 投稿日時 2014/6/5 17:45:45
>るきおさん
情報有難うございます。
FmLoginの説明が抜けていたので此方に記載します。失礼しました。
・Textbox1はユーザーID、TextBox2はパスワードの入力箇所
・btnGoはコードの実行。btnQuitは画面を閉じる事でプログラムを終了させる。

という仕組みになっています。

教えて貰った情報を元にコードを修正しましたが、上手く機能しません。
何処を訂正すれば宜しいでしょうか?

▼実行後に「入力文字列の形式が正しくありません」と表示された後に「ID,パスワードが一致しません」と表示▼

Imports System.Data.SqlClient

Public Class FmLogin
    Public Shared Property UserID As String
    Public Shared Property UserName As String
    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click

        If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
            Me.Hide()
            Dim Main As New fmmain
            Main.Label1.Text = FmLogin.UserName
            Main.ShowDialog(Me)
            Me.Show()
            Me.Close()
        Else
            MsgBox("ID,パスワードが一致しません")
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs Boolean
        Dim RESULT As String = 0

        Dim SQL As String = "SELECT ユーザー名 FROM T確定ユーザー情報" _
                            & " WHERE ユーザーID='" & id & "' AND パスワード='" & pass & "'"

        If RESULT IsNot Nothing Then
            FmLogin.UserID = id
            FmLogin.UserName = RESULT
        End If

        Using conn As New SqlConnection(ConnectString)
            Dim CMD As New SqlCommand(SQL, conn)
            Try
                conn.Open()
                RESULT = Convert.ToInt32(CMD.ExecuteScalar())
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using

        Return RESULT <> 0
    End Function

    Private Sub btnQuit_Click(sender As System.Object, e As System.EventArgs) Handles btnQuit.Click
        Me.Close()
    End Sub
End Class


▼実行前に※印の箇所で青い波線の「エラー 'IsNot' には参照型を持つオペランドが必要ですが、このオペランドの値型は 'Integer' です。」が発生▼
Imports System.Data.SqlClient

Public Class FmLogin
    Public Shared Property UserID As String
    Public Shared Property UserName As String
    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click

        If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
            Me.Hide()
            Dim Main As New fmmain
            Main.Label1.Text = FmLogin.UserName
            Main.ShowDialog(Me)
            Me.Show()
            Me.Close()
        Else
            MsgBox("ID,パスワードが一致しません")
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs Boolean
        Dim RESULT As Integer = 0

        Dim SQL As String = "SELECT ユーザー名 FROM T確定ユーザー情報" _
                            & " WHERE ユーザーID='" & id & "' AND パスワード='" & pass & "'"

        If ※RESULT※ IsNot Nothing Then
            FmLogin.UserID = id
            FmLogin.UserName = RESULT
        End If

        Using conn As New SqlConnection(ConnectString)
            Dim CMD As New SqlCommand(SQL, conn)
            Try
                conn.Open()
                RESULT = Convert.ToInt32(CMD.ExecuteScalar())
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using

        Return RESULT <> 0
    End Function

    Private Sub btnQuit_Click(sender As System.Object, e As System.EventArgs) Handles btnQuit.Click
        Me.Close()
    End Sub
End Class


>SQLインジェクションの脆弱性
Textbox1(ユーザーID)に「AND 1 = 1 --」と「OR 1 = 1 --」を両方入力後に登録しているTextbox2(パスワード)を入力させましたが、両方ログインに失敗しています。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/5 19:20:46
> Textbox1(ユーザーID)に「AND 1 = 1 --」と「OR 1 = 1 --」を両方入力後に

るきおさんが示されたのは
 ' OR 1 = 1 -- 
であって、
 OR 1 = 1 -- 
ではありませんよ。

つまり、
 『WHERE ユーザーID='★' AND パスワード='☆'』
の★側に、るきおさんの示された文字列を渡すと、
 『WHERE ユーザーID='' OR 1 = 1 --'AND パスワード='☆'』
という文字列が生成されることになります。

SQL Server では、「--」以降の部分をコメントと看做しますので、これは実質、
 『WHERE ユーザーID='' OR 1 = 1』
という問い合わせを意味します。(1 = 1) は常に TRUE ですから、
ユーザーID が何であれ、問い合わせが成功してしまうことになります。


> Me.Hide()
> Dim Main As New fmmain
> Main.Label1.Text = FmLogin.UserName
> Main.ShowDialog(Me)
> Me.Show()
> Me.Close()
ShowDialog で表示したフォームは、本来は Dispose が必要です。


それはさておき、こういった仕組みの場合は、ログインフォームをスタートアップフォームに
するのではなく、Sub Main() をスタートアップにして、そこからログインフォームを
稼動させた方が扱いやすいかもしれません。


> ▼実行後に「入力文字列の形式が正しくありません」と表示された後に
このメッセージを出力しているのは、恐らくは下記の部分でしょう。[CODE]  Try
      conn.Open()
      RESULT = Convert.ToInt32(CMD.ExecuteScalar())
  Catch ex As Exception
      MsgBox(ex.Message)
  End Try[CODE]

ExecuteScalar メソッドは、実行結果の先頭行の先頭列の情報を返します。
この場合の SQL の問い合わせは、「SELECT ユーザー名 FROM …」というものですよね。

ということは、たとえば、抽出されたユーザー名が 『123』 だった場合、
RESULT = Convert.ToInt32("123") として処理され、RESULT には 123 が格納されます。

しかし、抽出されたユーザー名が "ポチ" だった場合、
RESULT = Convert.ToInt32("ポチ") という数値変換は不可能なため、
FormatException の例外すなわち「入力文字列の形式が正しくありません。」を
発生させることになります。


問い合わせた「ユーザー名」から、どのような文字列が返されるのか、そして、
何のためにそのユーザー名を数値に変換しようとしたのかを意識しながら、
あらためてプログラムを書き直してみて下さい。


> ▼実行前に※印の箇所で青い波線の「エラー 'IsNot' には参照型を持つオペランドが必要ですが、このオペランドの値型は 'Integer' です。」が発生▼
提示の箇所には、不自然な点が 2 つあります。


一つはエラーメッセージにあるように、Integer 型変数に対して
IsNot 演算子で If 問い合わせを行おうとしている点です。

 るきおさんが紹介されたコードでは、変数 RESULT は自作クラス UserInfo を
 示していたので、Nothing 判定を行うための IsNot 演算子を使う意味もありますが、
 社会人さんが書かれたコードでは、変数 RESULT は Integer を示しているため、
 そもそも Nothing という状態にはなりえません。


もう一つは、そのIf 文を一体何のために記載しているのかです。

 そもそも、最初に「Dim RESULT As String = 0」で 0 がセットされてから、
 その If 文の行の到達するまでの間、変数 RESULT の内容を書き換えるような
 処理はどこにも無いように見受けられます。だとすれば、この If 比較は、
 何を目的として設置されたのでしょうか。



こうした点を再考してみれば、どのように修正するべきかも見えてくるかと思います。


(1) 変数 RESULT は、どのような状態を管理するために宣言しましたか?

 るきおさんが提案されたコードでは、現在入力されたユーザー情報を
 文字列として格納するためのクラスとして用意されていましたが、社会人さんが
 書かれたコードはそうではなく、ユーザー情報の認証状況を数値化して管理するための
 変数であるかのように見えます。


(2) その変数に、どういった値をセットしようとしていますか?

 そもそも、RESULT は Integer 型で本当に良いのでしょうか?
 もしも Integer 型で管理したいのだとしたら、その値が 0 だった場合には、
 どういう状態を表しているものとして扱いたいのでしょうか?


(3) その変数に値をセットするのはどの時点ですか?

 ユーザーがパスワード情報等を入力した時点でセットすべきでしょうか。それとも
 SQL Server に認証問い合わせを行った後の結果をセットすべきでしょうか?
 何が正しいのかは、変数 RESULT をどのような意図で用意しているのかによって異なってきます。
投稿者 社会人  (社会人) 投稿日時 2014/6/10 17:21:40
>魔界の仮面弁士さん

返事が遅くなりすみません。
自分の予想より話が難しくなっている為、確認したい事がいくつかあります。

>SQLの脆弱性について
指摘通りに、Textbox1に「' OR 1 = 1 -- 」、Textbox2はデータベース上に無い値を入力してもログイン出来るようになっていました。
ネットでいくつか調べてみると対策方法などが乗っていましたので、IFで両方のTextboxに「'」から始まる場合はSQLServerに参照せずにエラーメッセージの表示を考えています。
下のコードだと、片方に「' OR 1 = 1 --」を入力してもログイン失敗するようになりました。

※一旦、6月5日のコードに戻して修正しています。

Imports System.Data.SqlClient

Public Class FmLogin
    Dim FLG As Integer = 0
    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click

        If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
            Me.Hide()
            Dim Main As New FmMain
            Main.ShowDialog(Me)
            Me.Show()
            Me.Close()
        Else
            If FLG = 1 Then
                MsgBox("その入力方法は違法です。")
            Else
                MsgBox("ID,パスワードが一致しません")
            End If
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs Boolean
        Dim RESULT As Integer = 0
        Dim SQL As String = "SELECT COUNT(ユーザーID) FROM ユーザー情報" _
                                & " WHERE ユーザーID='" & id & "' AND パスワード='" & pass & "'"
        If 0 <= TextBox1.Text.IndexOf("'"Or 0 <= TextBox2.Text.IndexOf("'"Then
            FLG = 1
        Else
            Using conn As New SqlConnection(ConnectString)
                Dim CMD As New SqlCommand(SQL, conn)
                Try
                    conn.Open()
                    RESULT = Convert.ToInt32(CMD.ExecuteScalar())
                Catch ex As Exception
                    MsgBox(ex.Message)
                End Try
            End Using
        End If

        Return RESULT <> 0
    End Function

    Private Sub btnQuit_Click(sender As System.Object, e As System.EventArgs) Handles btnQuit.Click
        Me.Close()
    End Sub
End Class


>Sub Mainをスタートアップした後にログインフォームを呼ぶ。
Sum Mainの方でログインフォームを呼ぶ設定に修正しましたが、扱いやすい理由がピンと来ません。
メインフォーム経由のログインフォームより、ログインフォームの方が良いのでは無いかと思います。

>一つはエラーメッセージにあるように、Integer 型変数に対して、IsNot 演算子で If 問い合わせを行おうとしている点です。

根本的な話になりますが、

Public Class UserInfo
    Public Shared Property Id As String
    Public Shared Property UserName As String
End Class


るきおさんが示したコードは新規でフォームを作成し、そのフォーム名を「UserInfo」にして上記のコードを入力するという意味でしょうか?
もしそうだとすると、「UserInfo」を現在作成中の「FmLogin」のコードに組む込む事と勘違いして入力していました。

>もう一つは、そのIf 文を一体何のために記載しているのかです。
恐らくですが、ユーザー名が無いデータを探していると思います。
SQLServerにはID、パスワード、ユーザー名は全て入力されています。


>(1) 変数 RESULT は、どのような状態を管理するために宣言しましたか?
元は、半角英数字のみで作られたパスワードとIDの二つだけを参照するログインコードでした。
その場所は現時点で一回も修正していません。
ここまで、影響するとは思っていませんでした。

>(2) その変数に、どういった値をセットしようとしていますか?
RESULTはユーザー名(山田太郎などの日本人の名前)を値を入力したいと思っていますのでstring型でセットしたいと思っています。

>(3) その変数に値をセットするのはどの時点ですか?
自分の考えでは、遅くてもFmMainが表示される前にユーザーIDとユーザー名をセットしたいと思っています。


投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/10 21:21:16
> 指摘通りに、Textbox1に「' OR 1 = 1 -- 」、Textbox2はデータベース上に無い値を入力してもログイン出来るようになっていました。

その問題もありますが、そもそも今のコードだと、
ユーザー名が「McDonald's」だった場合にエラーになります。
TextBox1=「'」TextBox2=「」でもダメでしょうね。

問題となっているのは「'」の文字なわけですが、この場合には、
  sql = "SELECT COUNT(ユーザーID) FROM ユーザー情報" _
     & " WHERE ユーザーID='" & Replace(id, "'""''") & "' AND パスワード='" & Replace(pass, "'""''") & "'"

のように処理することで、こうした問題を回避できます。

なお、こうしたパラメータを SqlCommand の Parameters 経由で渡すようにすれば、
データ型のチェックも行えるため、より安全・確実となります。



>>Sub Mainをスタートアップした後にログインフォームを呼ぶ。
> Sum Mainの方でログインフォームを呼ぶ設定に修正しましたが、扱いやすい理由がピンと来ません。
Sum ではなく Sub ですね。


> メインフォーム経由のログインフォームより、ログインフォームの方が良いのでは無いかと思います。
メインフォームをスタートアップにするのも、
ログインフォームをスタートアップにするのも、
どちらも適切ではない、と思います。(不正解ではありませんが)

私の提案は、いずれかのフォームをスタートアップにするのではなく、
フォーム非依存の Main プロシージャから始めるという処理方法です。

http://msdn.microsoft.com/ja-jp/library/ms235406.aspx
http://www.atmarkit.co.jp/fdotnet/dotnettips/524vb2005main/vb2005main.html
http://dobon.net/vb/dotnet/programing/makeentrypoint.html


というのも、現状のコードだと、Show/Hide/DialogResult/Close の使い分けが
適切ではないように思ったからです。

-------
現行のコードでは、まず最初にログインフォームを起動させていますよね。
ログインが完了したら、ログインフォームは不要なため、画面から消します。

ここまでは OK ですが、そのためにログインフォームを Hide しているのが
なんだか不自然に思えたのです。ログイン画面を度々「再表示」する必要が
あるのなら Hide を使うこともあるでしょうが、今回はそうでは無いですよね。

現在の UserID や UserName を管理しているという事情があるにしても、
提示されたコードでは、それらが Shared で宣言されているのですから、
ログインフォームをいつまでも残しておく必然性は無いはずです。

にも関わらず、Hide を使わざるを得なかったのは、ログインフォームが
スタートアップになっていたが故の弊害であろうと予測しました。

終了直前に、わざわざ Show → Close という「一瞬表示させてから閉じる」
という不自然な実装になっているのも同様の理由。

スタートアップフォームを閉じるとアプリが終了してしまうという問題を
どうにか回避させようと試行錯誤した結果ではないかと予想したのです。


そして何よりも、実際のメイン処理の画面となる fmBase や fMmain が、
モーダルフォームとして DialogResult で呼ばれているのが不自然です。
フォームが一つだけならば、本来はモードレスで十分なはずですから。


スタートアップの設定を見直すことで、これらの不自然さを解消できるかと。


> るきおさんが示したコードは新規でフォームを作成し、そのフォーム名を「UserInfo」にして
るきおさんは、
『IDとユーザー名を保存するためのクラスを定義します』
と書かれていますが、「フォームを作成」とは書いていないようです。


これは、『ユーザー情報を管理するためのクラス(あるいは構造体)』を
用意することを想定したものでしょう。その役目を、Form クラスに
兼任させることはできますが、それを意図したものでは無さそうです。


たとえば、「現在ログイン中のユーザー名」を管理するために、
Public Module Module1
 Public UserName As String
End Module
あるいは
といった、アプリケーション全体で利用可能な変数を用意したとします。
(クラスの Shared 変数でも可)


管理するのがユーザ名だけであれば、String 変数一つでも十分ですが、

・ログインに使うのは「ログインID」と「パスワード」だが、
 以降の画面では「ユーザーID」と「表示用ユーザー名」を利用したい。

などのように、1 つのアカウントに対して、複数の付随情報がある場合には、
その情報一つ一つに個別の変数を用意していると、管理が煩雑になってしまいます。

そこで、それらを一つの変数で管理できるよう、「UserInfoクラス」を
作成するということです。


>> もう一つは、そのIf 文を一体何のために記載しているのかです。
> 恐らくですが、
ご自身で記載されたコードなのに、その本人自らが「恐らく」といった
曖昧な理解ではまずいと思いますよ。(^^;

実装されたコード実装が正しいかどうかど、その処理をどういう目的で
記述したのかという『意図』は別物ですし。



>> その If 文の行の到達するまでの間、変数 RESULT の内容を書き換えるような
>> 処理はどこにも無いように見受けられます。だとすれば、この If 比較は、
>> 何を目的として設置されたのでしょうか。
> ユーザー名が無いデータを探していると思います。

『If ※RESULT※ IsNot Nothing Then』の部分は、
『「ユーザー名が無いデータ」が存在した場合』を
意図したコードということですね?

ということは、先の部分のコードは、下記のような意味になるのでしょうか。

Dim RESULT As Integer = 0   '初期値として「0」をセット。(……本来は String???) 
Dim SQL As String = ……          '問い合わせ用の SQL 文を作成 

'上記の SQL を実行するまえに、 
'下記の If チェックを実施する 

If ※RESULT※ IsNot Nothing Then  '←「ユーザー名が無いデータ」を探すための If 文??? 
 '「ユーザー名が無いデータ」だった場合に、下記の処理を実行。 
 FmLogin.UserID = id         '入力されたユーザーIDを保持。 
 FmLogin.UserName = RESULT   'ユーザー名として、初期値「0」をセット??? 
End If

'以下で、先の SQL を実行する 
Using conn As New SqlConnection(ConnectString)


説明された意図に従ってコメントをつけたつもりなのですが、
まだしっくり来ません…。説明された「意図」あるいは実際のコードの
いずれか(あるいは両方)が間違ってはいないでしょうか。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/10 22:29:31
>> メインフォーム経由のログインフォームより、ログインフォームの方が良いのでは無いかと思います。
> メインフォームをスタートアップにするのも、
> ログインフォームをスタートアップにするのも、
> どちらも適切ではない、と思います。(不正解ではありませんが)

参考までに、スタートアップを Sub Main にした場合のコード案を書いてみました。
るきおさんの UserInfo クラスを用意する案も盛り込んでいます。

' fmLogin_Load や fmBase_Load から始めるのではなく、Sub Main から始めるように設定しておく 
Public Sub Main()
 Application.EnableVisualStyles()
 Application.SetCompatibleTextRenderingDefault(False)

 '現在のユーザー情報は、るきおさん提案の UserInfo クラスで管理する 
 Dim account As UserInfo = Nothing '未ログイン時には、初期値として Nothing にしておく 

 '---- ログイン処理 ---- 
 Using login As New fmLogin()
  'ログイン画面をモーダルで表示する 
  If login.ShowDialog( ) = System.Windows.Forms.DialogResult.OK Then
   '認証が終わったら、認証結果を UserInfo クラスとして受け取る 
   account = login.LoginAccount   '←新たにLoginAccount というプロパティを自作 
  Else
   '認証がキャンセルされたり、入力ミスが続いたときは、メイン画面に移行せずにアプリ終了 
   Return
  End If
 End Using

 '---- メイン画面 ---- 
 If account IsNot Nothing Then
  '認証が済んだら、その認証結果情報(UserInfoクラス)を 
  'メイン画面に引き渡して表示する。 
  Application.Run( New fmMain( account ) ) 'コンストラクタを追加 
 End If

End Sub



【ログインダイアログ】
・フォーム名を fmLogin と仮定しました。
・認証の成否やキャンセル判定に、DialogResult を利用しています。
・モーダルフォームの呼び出しなので、Using ブロックを利用しています。
・ログイン結果(UserInfo)の返却のため、LoginAccount プロパティを自作しています。


【メイン画面】
・フォーム名を「fmMain」と仮定しました。
・標準では、引数無しの New しか使えませんが、上記コードではfmMain に
 『Sub New(account As UserInfo)』のコンストラクタを追加する前提で記述しています。
・コンストラクタではなく、メソッドやプロパティ、あるいはフィールド変数などで
 受け渡すこともできます。ですが、Load 時点で使えるのようにすることを
 考えると、コンストラクタで渡しておくのが良いであろうという判断です。


【SQL Server への問い合わせ】
・今回のメインどころではありますが、上記ではあえて省略しています。
・今までのコードを見ると、CMD.ExecuteScalar() で問い合わせるまでは合っていますが、
 何故か、データベースから返されるユーザー名(山田太郎などの日本人の名前)を、
 String で受け取るのではなく、ToInt32 で数値変換しようとされていました。流石に
 "山田太郎" という文字列を RESULT As Integer に代入するのは不可能なので、
 その部分は見直して下さい。
・実際の運用では、サーバーへの接続に失敗した場合などに備えて、
 Try~Catch 構文を併用することが望ましいです。


【UserInfo クラス】
・宣言方法については、るきおさんが解説されているので省略します。
・認証時にセットしたユーザー名などの情報を、メイン画面等から
 どのように再利用するかは御随意に。
・認証が成功するたびに、新たに UserInfo のインスタンスが生成されます。工夫すれば、
 SQL Server Management Studio のように、複数ユーザーでマルチログインするなどの
 応用も効くかと思います。


【スタートアップ】
・スタートアップフォームは用いず、Sub Main からの実行にしています。
 これにより、スタートアップフォームを動的に変更できるなどの融通さが生まれます。
・たとえば、「通常はログイン画面が初期表示されるが、認証情報をコマンドライン引数で
 指定した場合には、いきなりメイン画面からスタートさせる」なんてことも可能です。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/10 23:24:45
> ログイン成功後、IDとユーザー名を各フォームで使用したい

先のコードでは、ログイン成功後の「IDとユーザー名」を、
Sub Main 内のローカル変数『Dim account As UserInfo』で管理し、
表示する各画面に、その情報を受け渡すという方針を採っています。

SQL Server の Management Studio は、複数のユーザーで接続が可能ですが、
それと同様に、将来的にマルチユーザーログインが可能なアプリケーションが求められても、
 fAdmin = New fmMain( 管理者アカウント )
 fAdmin.Show()
 fGuest = New fmMain( 体験版アカウント )
 fGuest.Show()
のようにして、同じ画面を認証ユーザーごとに並行して開くといった芸当も可能になります。



一方、常にシングルログインしか認めないようなアプリであれば、
現在のログイン情報を、一箇所にまとめて管理しておいた方が便利です。

その場合は、この account As UserInfo な変数を、ローカル変数ではなく
アプリケーション全体から見えるような場所に配置すると良いでしょう。

たとえば、Module を用意してそこに Public Account As UserInfo などと宣言するとか、
ログイン画面自体の Shared メンバーとして Public Shared Account As UserInfo とするとか、
あるいは、UserInfo クラスそのものをシングルトンクラスとするなどの手法です。


このほか、現在のユーザー情報を My.User.CurrentPrincipal プロパティの
プリンシパルで管理するという方法もあります。この方法は、SQL Server に
Windows 認証で繋いでいる場合に丁度良いかもしれません。
投稿者 社会人  (社会人) 投稿日時 2014/6/12 13:58:33

※今までログイン成功時に移動するFmMainをFmBaseに名前を変更しました。

>その問題もありますが、そもそも今のコードだと、ユーザー名が「McDonald's」だった場合にエラーになります。

一つ確認です。
ログインの際にTextBox1にユーザーID(1000,2000)、TextBox2にパスワード(1A450,98BOC)の半角英数字と半角のローマ字の二種類のみの構成になっています。
記号(&、%、$、¥、')などは使用していませんが、問題を回避する理由はユーザー名を取り出す際になんらかの問題やエラーが起こらないようにする為の対策でしょうか?
今、ログインをする際にユーザー名を使用する意味で捉えていますが大丈夫でしょうか?

>フォーム非依存の Main プロシージャから始めるという処理方法です。

http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=17599を参考に、「プロジェクト」→「項目の追加」→「クラス」で新規作成するという事でしょうか?


> Hide しているのがなんだか不自然に思えたのです。

FmLoginをHideする理由なんですが、実際にフォーム移動した際にFmLoginが二つ表示されるようになっています。
原因は分かりませんが、FmLoginでHideした後にFmBaseでFmLoginをCloseをするとログイン認証画面が消え、画面上にはFmBaseだけが表示されます。
FmLoginのShowとCloseはコメント化にして動作確認をして変化が無かったので消しました。

>モーダルフォームとして DialogResult で呼ばれているのが不自然です。
最初にいくつか確認なんですが
・DialogResultは、何かしらのデータを保存する際に使用するコード?
・モーダルフォームを展開中は、他のフォームをアクティブやデータの加工が出来ない。
・モードレスフォームを展開中は、他のフォームをアクティブやデータの加工は出来る
という認識で当たっていますか?
知識不足で失礼ですが、質問の意味が良く分かりません。
もう少し詳しい説明をして頂くと助かります。

>そこで、それらを一つの変数で管理できるよう、「UserInfoクラス」を作成するということです。

分かりました。既に作ってしまった「FmUserInfo」を削除して、プログラムを実行して早い段階で「UserInfo」クラスの設定を行うように設定します。

>曖昧な理解ではまずいと思いますよ。(^^;
すみません。
以前、教えて頂いたコードをコピーして自分の環境に合わせると動くようになったのでそこで詳しい内容を聞かずして終わりにしてしまいました。
教えた頂いた際に提示した設定は「ユーザーIDとかユーザー名を抜き出す必要は無いから、単純にユーザーIDとパスワードが合致した場合だけログインする」という内容で、RESULTをInteger型に設定しています。

>ということは、先の部分のコードは、下記のような意味になるのでしょうか。
コードは上から下に流れるのが基本ですが、何故、SQLを実行する前に IF RESULTが先に実行されるのでしょうか?
魔界の仮面弁士さんが指摘するまで、SQL実行後にIF RESULTが実行されると思ってました。


投稿者 るきお  (社会人) 投稿日時 2014/6/12 21:57:27
いろいろな話が出ているので、なかなか読むのが大変です。

話題は3つでしょうか。
1.IDとパスワードを使ってユーザーをログインさせ、IDとユーザー名を保持し続ける方法。
2.SQLの脆弱性
3.アプリケーションをどのように開始して、フォームをどのように表示するか。

1つずつ解決していくのが良いと思いますよ。
3→1→2の順番がやりやすいかと思います。

3の具体例は魔界の仮面弁士さんが書いていますね。
2014/6/10 22:29:31  の投稿です。

これを中心にプロトタイプを作って動きを検証してみてはどうでしょうか?
このコードは、プロジェクトを右クリックして、[追加] - [モジュール] でモジュールを追加して、その中に書くと良いでしょう。モジュールの名前は既定ではModule1 です。名前を変えても変えなくても良いです。

そして、プロジェクトのプロパティで
「アプリケーションフレームワークを有効にする」のチェックを『はずして』から、
スタートアップオブジェクトで Sub Main を選択すると Sub Mainから開始できます。

このことは魔界の仮面弁士さんが紹介しているリンク先にも画像つきで説明されています。
http://www.atmarkit.co.jp/fdotnet/dotnettips/524vb2005main/vb2005main.html
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/12 23:13:21
> コードは上から下に流れるのが基本ですが、
> 何故、SQLを実行する前に IF RESULTが先に実行されるのでしょうか?
そのタイミングでは「SQL文を作成」しただけです。SQLの「実行」はされていません。

作成した SQL 文を SqlCommand に渡し、それを ExecuteScalar することによって、
ようやく「SQL が実行」されることになります。


> 魔界の仮面弁士さんが指摘するまで、SQL実行後にIF RESULTが実行されると思ってました。
少なくとも、RESULT に値をセットしているのは
>> RESULT = Convert.ToInt32(CMD.ExecuteScalar())
の行なのですから、If RESULT はそれよりも下に置く必要があります。

それより前の行では RESULT の値は変化しないため、そもそも判定文自体が不要です。


> 教えた頂いた際に提示した設定は「ユーザーIDとかユーザー名を抜き出す必要は無いから、
> 単純にユーザーIDとパスワードが合致した場合だけログインする」という内容で、
> RESULTをInteger型に設定しています。

たとえば、「まだログインしていないのか、それともログイン済みなのか」を管理するのなら、
True / False の二値で良いので、Boolean 型で十分です。

もしも Integer 型にするのであれば、それぞれの値にどのような意味を持たせるのかが
重要です。仮に「0 だったら未ログイン」という意味にするのなら、0 以外はどういう意味なのか。
それが「1」の場合と「2」の場合とで意味が異なるのかどうかを考えておきましょう。

いずれにせよ、その後の実装では、2014/6/10 17:21:40 でご自身が書かれていたように、
『ユーザー名(山田太郎などの日本人の名前)を値を入力したいと思っていますのでstring型でセットしたい』
ということで、String 型にされるのですよね。であればこの場合、その変数が空であれば
ユーザー名がセットされていない状態…すなわち未ログインの状態ということですね。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/12 23:41:29
>> ユーザー名が「McDonald's」だった場合にエラーになります。
> 今、ログインをする際にユーザー名を使用する意味で捉えていますが大丈夫でしょうか?
ごめんなさい、ユーザー名ではなく、「パスワードもしくはユーザーIDが『McDonald's』だった場合」と読み替えてください。


> 問題を回避する理由はユーザー名を取り出す際になんらかの問題やエラーが起こらないようにする為の対策でしょうか?
はい、その通りです。特にここは認証に関わる部分ということもあり、、
間違ったパスワードで処理を通過してしまっては、都合が悪いですからね。


> 記号(&、%、$、¥、')などは使用していませんが、
文字種をあらかじめ制限しているのですね。

不正な文字が渡されないよう、ガードするようなコードを用意してあるのなら、
先のSQLの脆弱性の問題は起こりませんので、Replace 置換などの処理を
省略しても大丈夫です。


> TextBox1にユーザーID(1000,2000)
ちなみに、TextBox1 に数字しか入力できないようにしたいという場合、
KeyPress イベントで入力文字種を制限する手法が良く知られています。
http://dobon.net/vb/dotnet/control/numerictextbox.html

しかしこの方法だと、キーボードからの入力は制限する事ができても、
クリップボードからの貼り付けは防げないという欠点がありますのでご注意ください。

(上記サイトでは、クリップボード対策についても記載されていますが、他にも、
 音声認識入力、手書き文字認識などといった方法で入力される可能性もあります)

あるいは、Validating イベントで値をチェックする方法や、ログインボタン押下時に、
TextBox1.Text の内容を検査して、不正な文字が含まれていた場合にはログインさせず、
再入力を促す警告メッセージを表示するといった手法、あるいは標準の TextBox の代わりに、
数値しか入力できないコントロールで代用するといった手法もありますね。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/13 00:36:15
>>フォーム非依存の Main プロシージャから始めるという処理方法です。
> http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=17599を参考に、
>「プロジェクト」→「項目の追加」→「クラス」で新規作成するという事でしょうか?

『スタートアップ』を変更して、Form_Load ではなく Main から開始するように
変更するということです。
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=10588


> FmLoginをHideする理由なんですが、実際にフォーム移動した際にFmLoginが二つ表示されるようになっています。
でもそれは、意図した動作では無いのですね?
どこかで、誤って FmLogin のフォームインスタンスをもう一つ生成し、
それを表示してしまっているのかも知れません。


> FmLoginのShowとCloseはコメント化にして動作確認をして変化が無かったので消しました。
もしそれが、 2014/6/5 17:45:45 時点のコードを
Me.Hide()
Dim Main As New fmmain
Main.Label1.Text = FmLogin.UserName
Main.ShowDialog(Me)
'Me.Show()    '除去 
'Me.Close()    '除去 
のように変更したのだとしたら、もしかしたら、少々問題があるかもしれません。

たとえば新規プロジェクトで、下記を実行してみて下さい。
Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Me.Hide()
        Using f As New Form()
            f.ShowDialog()
        End Using
        'Me.Close() 
    End Sub
End Class


ボタンを押すとその画面は消えて、代わりに別のフォームが表示されます。
その後、その別画面を×で閉じてみてください。

そうすると、表示されている画面は一つもないのに、アプリケーションは
実行されたままになってしまいます。

開発環境から実行した場合、再生ボタン▶は使えず、停止ボタン■や一時停止ボタン▮▮が
使える状態になっていることでしょう。
(最後の Close のコメントを解除した場合は、フォームを閉じたときにアプリも終了します)


> ・モーダルフォームを展開中は、他のフォームをアクティブやデータの加工が出来ない。
> ・モードレスフォームを展開中は、他のフォームをアクティブやデータの加工は出来る
> という認識で当たっていますか?
その通りです。「『他のフォーム』の操作ができるかどうか」が鍵となります。

つまり「他のフォーム」が表示されていない状態の場合、本来はわざわざ
モーダルにする必要が無いのです。


> ・DialogResultは、何かしらのデータを保存する際に使用するコード?
DialogResult とは要するに、「メッセージボックス」や「ダイアログ」の戻り値のことです。

標準の MessageBox.Show や MsgBox で利用されているほか、
モーダルダイアログを自作する際にも使われます。


この値は、ユーザーが押したボタンが、OK / Cancel / はい / いいえ / 中止 / 再試行 / 無視 の
いずれであったのかを表すものです。
(Form の DialogResult プロパティは、ShowDialogResult の戻り値と連動しています)

たとえば、ログイン画面が表示された時、ユーザーはログイン作業を中止するために、
キャンセルボタンを押したり、あるいはログイン画面を右上の×で閉じたりするかも知れません。

そのような場合、呼び出し側では
 x = ログイン画面.ShowDialog()
 If x = DialogResult.OK Then
  'ログインが行われた時の処理 
 ElseIf x = DialogResult.Cancel Then
  'ログイン画面をキャンセルした時の処理 
 ElseIf …
のようにして、キャンセルされたかどうかを判定できるわけです。

この時、呼び出されたログイン画面側では、ログイン成功時に Me.DialogResult に OK を
セットし、ログインせずに閉じられる場合は、Cancel をセットするように実装します。 

なお、そのログイン画面では、OK ボタンの DialogResult プロパティを OK に設定し、
キャンセルボタンの DialogResult プロパティを Cancel に設定しておくと便利です。

そうすると、キャンセルボタンの Click イベントを一切書かなかったとしても、
「キャンセルボタンが押されたら、自フォームの DialogResult プロパティに Cancel をセットし、自画面を閉じる」
に相当する処理が自動的に行われるようになるからです。(もちろん、Click イベントを併用しても構いません)


なお、DialogResult プロパティが利用されるのは、モーダルフォームのみです。
モードレスで呼び出されたフォームでは使いません。
投稿者 社会人  (社会人) 投稿日時 2014/6/19 14:46:00
るきおさん
>話題は3つでしょうか?

そうです。提示されている3項目をメインに話を進めています。
Module1の作成方法についての説明有難うございます。
無事に作成出来ました。

魔界の仮面弁士さん
>「SQLが実行」されることになります。

説明有難うございます。
SQL実行のタイミングを間違って覚えていました。

>「パスワードもしくはユーザーIDが『McDonald's』だった場合」と読み替えてください
>間違ったパスワードで処理を通過してしまっては、都合が悪いですからね。
>文字種をあらかじめ制限しているのですね。

パスワードとIDを作成する際に一定の法則があり。
パスワードは半角英語を一つと半角数字の組み合わせでの構成。(Q541,21C5,など)
IDは英数字のみの構成。(5021,4895など)となっています。
今の所、ID、パスワード作成時にある程度のセキュリティを高めていると思っています。
魔界の仮面弁士さんが提示する状況になっていませんが、コードの修正は必要でしょうか?

>クリップボードからの貼りつけは防げない欠点がある
情報有難うございます。
最初にログインコードを完成させてから作成しようと思います。

>FmLoginのフォームインスタンスをもう一つ生成し表示し、それを表示してしまっているのかも知れません。
>新規プロジェクトで下記を実行してみてください。
教えて頂いたコードを少し修正しただけで、問題解決しました。
有難うございます。
投稿者 社会人  (社会人) 投稿日時 2014/6/19 14:46:29
るきおさん
>話題は3つでしょうか?

そうです。提示されている3項目をメインに話を進めています。
Module1の作成方法についての説明有難うございます。
無事に作成出来ました。

魔界の仮面弁士さん
>「SQLが実行」されることになります。

説明有難うございます。
SQL実行のタイミングを間違って覚えていました。

>「パスワードもしくはユーザーIDが『McDonald's』だった場合」と読み替えてください
>間違ったパスワードで処理を通過してしまっては、都合が悪いですからね。
>文字種をあらかじめ制限しているのですね。

パスワードとIDを作成する際に一定の法則があり。
パスワードは半角英語を一つと半角数字の組み合わせでの構成。(Q541,21C5,など)
IDは英数字のみの構成。(5021,4895など)となっています。
今の所、ID、パスワード作成時にある程度のセキュリティを高めていると思っています。
魔界の仮面弁士さんが提示する状況になっていませんが、コードの修正は必要でしょうか?

>クリップボードからの貼りつけは防げない欠点がある
情報有難うございます。
最初にログインコードを完成させてから作成しようと思います。

>FmLoginのフォームインスタンスをもう一つ生成し表示し、それを表示してしまっているのかも知れません。
>新規プロジェクトで下記を実行してみてください。
教えて頂いたコードを少し修正しただけで、ログインの問題解決しました。
有難うございます。
投稿者 社会人  (社会人) 投稿日時 2014/6/19 15:12:36
※すみません。上の二つの内一番上の書き込みミスをしました。正しくは下の書き込みです。

ログイン成功時にログイン画面が二つ表示される問題は解決したのは、最初はこういう書き方でしたが、

Public Class FMBase

    Private Sub FMBase_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Dim Login As new FmLogin
       Me.Hide()
       Login.show
    End Sub
End Class


TextboxとIF関数を使う事で解決しました
※Textbox1はtextのプロパティで「0」を最初から入れています。

Public Class FMBase

    Private Sub FMBase_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        If TextBox1.Text = 0 Then
            Me.Hide()
            Using f As New fmlogin
                f.ShowDialog()
            End Using
            Me.Close()
        Else
            Me.Show()
        End If
    End Sub
End Class


問題のログインコードですが、いくつか修正して「入力文字列の形式が違う」というエラーが無くなりましたが、まだログイン出来ていない状況です。

Imports System.Data.SqlClient

Public Class fmlogin
        Dim FLG As Integer = 0
    Dim BBB As Boolean
    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"

    Public Class UserInfo
        Public Shared Property ID As String
        Public Shared Property UserName As String
    End Class

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click

        If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
            Me.Hide()
            Dim Main As New FMBase
            Main.TextBox1.Text = 1
            Main.Show()
        Else
            If FLG = 1 Then
                MsgBox("その入力方法は違法です。")
            Else
                MsgBox("ID,パスワードが一致しません")
            End If
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs Boolean
        Dim RESULT As String 'IntegerからStringに変更。 
        Dim SQL As String = "SELECT ユーザー名 FROM ユーザー情報 WHERE ユーザーID='" & id & "' AND パスワード='" & pass & "'"

        If 0 <= TextBox1.Text.IndexOf("'"Or 0 <= TextBox2.Text.IndexOf("'"Then
            FLG = 1
        Else
            Using conn As New SqlConnection(ConnectString)
                Dim CMD As New SqlCommand(SQL, conn)
                Try
                    conn.Open()
                    RESULT = Convert.ToString(CMD.ExecuteScalar()) 'Convert.ToInt32からConvert.ToStringに変更 
                                    Catch ex As Exception
                    MsgBox(ex.Message)
                End Try
            End Using
        End If

        Return BBB '「RESULT <> 0」にするとString "横横太郎       " から型 'Boolean' への変換は無効です。とエラー。エラー回避の為にBBBを使用 
    End Function


投稿者 HiDE-Ada  (社会人) 投稿日時 2014/6/19 18:11:49
先の投稿に中途半端な回答を提示してしまって申し訳ないです。
問題が大きくなってしまい、すみません。

上のソースでログインできないのは、せっかく
 RESULT = Convert.ToString(CMD.ExecuteScalar())
としてユーザー名を取得しているのに
 Return BBB
としているためです。
それも、BBBには値が代入されていないので常にFalseだからです。
RESULTにはユーザ名(取得成功時)かNothing(失敗時)なので
 Return RESULT IsNot Nothing
で、ログインはできるかと思います。
取得したユーザ名が必要なはずなので、
 RESULT = Convert.ToString(CMD.ExecuteScalar())
 with UserInfo
  .ID = id
  .UserName = RESULT
 end with
としておきましょう。

またClass UserInfoは、UserInfo.vbとして一つのクラスファイルとして
定義した方がいいです。

また、FMBase_LoadでLogIn.showとしているということはスタートアップフォーム
がFMBaseだと思われますが、先の回答はFMLoginをスタートアップフォーム
とした場合でした。
もしどうしてもFMBaseをスタートアップフォームにしたいのであれば
        If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
            Me.DialogResult = Windows.Forms.DialogResult.OK 'ShowDialogの戻り値 
            Me.Close
        Else

で十分です。当然
    Private Sub FMBase_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        me.hide   'FMBaseが表示されていてもよければ、必要なし 
        using Login As new FmLogin
          if Login.showdialog(Me) <> Windows.Forms.DialogResult.OK then
            me.close
          end if
        End using
        me.show 'me.hideしないなら、必要なし 
    End Sub

でログイン成功時以外は、終了させる必要があります。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2014/6/19 20:38:19
> ※Textbox1はtextのプロパティで「0」を最初から入れています。
> If TextBox1.Text = 0 Then

これは非常にマズイです。

Text プロパティが返すのは 文字列(String 型)です。
しかし上記の比較式では、右辺の「0」が整数値(Integer 型)になっていますよね。

型をあわせるために、右辺も文字列型にして、
 『If TextBox1.Text = "0" Then』
と書くようにしましょう。 


『If TextBox1.Text = 0 Then』という記述の場合、VB は「=」の両辺を
数値(Double 型)に変換してから比較する仕様になっています。

すなわち上記は、「If CDbl(TextBox1.Text) = 0.0 Then」という意味で
実行されてしまっているということです。


TextBox1 の内容が、初期値に指定した「0」のままであるとか、あるいは
「123」などといった数値として記入されている場合には、現在の
『If TextBox1.Text = 0 Then』でのチェックを通過できます。しかし、もしも
TextBox1 の内容が、「」や「abc」などに変更された場合、
『If TextBox1.Text = 0 Then』のコードのままでは、Double 型への
型変換エラーが実行時に発生してしまうことになります。


> 今の所、ID、パスワード作成時にある程度のセキュリティを高めていると思っています。

それは「作成時」の法則であって、「入力時」の制限ではありませんよね。

本来のIDやパスワードはそういうルールであったとしても、利用者はそれらと異なる文字列を、
【間違えて入力してしまう】(あるいは意図的な誤入力などの)恐れがあります。

その場合に誤動作やエラーが起きぬようにするための対策として
 ・範囲外の文字が入力できないように、画面側に入力制限を施す
 ・データベースに渡す前に入力値検査を行うようにする
あるいは
 ・「'」を「''」に置き換えるか、パラメータクエリーを用いる
といった対処が必要になってくるわけです。



> Private Function CheckUser(id As String, pass As String) As Boolean
この CheckUser 関数は、戻り値として何を返したいのでしょうか?

ログインできたかどうかを、True/False で返したいなら、As Boolean で良いですが、
ログインされたユーザー名を文字列として返したいなら、As String にするべきです。


> Return BBB '「RESULT <> 0」にするとString "横横太郎       " から型 'Boolean' への変換は無効です。とエラー。

いいえ、そうはならないはずです。

もしも『String "横横太郎       " から型 'Boolean' への変換は無効です。』
というエラーが出力されたなら、それは「Return RESULT」と書いた場合でしょう。

「Return RESULT <> 0」としたのなら、エラーメッセージは
  『String "横横太郎       " から型 'Double' への変換は無効です。』
となるはずです。


> エラー回避の為にBBBを使用 

修正の方向性が間違っています。

RESULT には、ログインしたユーザー名(文字列)を格納したのですよね。
であれば、Function CheckUser の戻り値も As String にしておくべきです。


なお、As Boolean のままにしておきたいのであれば、ログインできたかどうかを確認するため、
「Return Not String.IsNullOrEmpty(RESULT)」などと書くことができます。
投稿者 HiDE-Ada  (社会人) 投稿日時 2014/6/20 17:38:05
はぁ、またミスってました。こんなの書くから・・・
    Private Sub FMBase_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        me.hide   'FMBaseが表示されていてもよければ、必要なし  
        using Login As new FmLogin
          if Login.showdialog(Me) <> Windows.Forms.DialogResult.OK then
            me.close
          end if
        End using
        me.show 'me.hideしないなら、必要なし  
    End Sub

のme.hide/me.showは意味がないですね。LoadイベントではMeはまだ表示されていなかったはず?

それと、スタートアップフォームという名前はプロジェクトのプロパティで
「アプリケーションフレームワークを有効にする」
にチェックがある場合で、チェックをはずすとスタートアップオブジェクトへと表示が変わります^^;
投稿者 社会人  (社会人) 投稿日時 2014/6/24 11:35:56

>HiDE-Adaさん
HiDE-Adaさんの責任では無いです。
迷惑をかけて本当にすみません。

>ログインはできるかと思います。
Hide-Adaさんの指示通りに入力しましたが、

 with UserInfo
.ID = id
.UserName = RESULT
 end with



の.IDと.UserNameの箇所で「Login.UserInfoのメンバーではありません」というエラーが表示されてしまいます。
残りのスタートアップフォームの指摘は問題ありませんでしたが、FMLogin,FMBase,UserInfoの全コードを記載します。

>オブジェクトへと表示が変わります。
チェックを外してModuke1.vbの作成しました。

>魔界の仮面弁士さん

>これは非常にマズイです。
TextBoxの値でプログラムを実行してから初めてFMBaseを読み込んだのか、それともFMLoginからFMBaseの読み込みなのかの判断を付けようと考えていましたが、Hide-Adaさんの指示を受けてコードの修正を行うと自分が欲しい流れになりました。
(最初にFMBaseを読み込んだ際にFMBase画面を非表示にし、FMLoign画面を表示。ログイン成功時にはFMLogin画面を非表示にしFMBase画面を表示させる)
細かい説明まで頂いて大変失礼ですが、Hide-Adaさんの案を取る事にします。

>利用者はそれらと異なる文字列を、【間違えて入力してしまう】(あるいは意図的な誤入力などの)恐れがあります

自分が言っていたのでは確かに「作成時」の法則で「入力時」の制限ではありませんでした。
SQLの脆弱性の事ばかり考えていたので、利用者側の目線から見ずに問題解決していたと思っていました。
指摘を受けて修正しました。

>この CheckUser 関数は、戻り値として何を返したいのでしょうか?
ユーザー名を文字列で返したいので As Stringに変更しました。

> Return BBB '「RESULT <> 0」にするとString "横横太郎       " から型 'Boolean' への変換は無効です。とエラー。

すみません。此方の入力ミスです。
指摘通りに、
「RESULT <> 0」にするとString "横横太郎       " から型 'Double' への変換は無効です。
が正しいエラー内容でした。

>修正の方向性が間違っています。
Function CheckUserの戻り値と言うのは、戻り値として何を返したいのでしょうか?の質問で As Stringに変更したので大丈夫でしょうか?
投稿者 社会人  (社会人) 投稿日時 2014/6/24 11:37:49

ここから下は修正したコードです。

▼UserInfo.vb▼

Public Class UserInfo
    Public Class Userinfo
        Public Shared Property ID As String
        Public Shared Property UserName As String
    End Class
End Class


▼FMBase.vb▼
Public Class FMBase

    Private Sub FMBase_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Using Login As New fmlogin
            If Login.ShowDialog(Me) <> Windows.Forms.DialogResult.OK Then
                Me.Close()
            End If
        End Using
    End Sub
End Class


▼FMLogin.vb▼

Imports System.Data.SqlClient

Public Class fmlogin

    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click
        If TextBox1.Text.Length = 0 Or TextBox2.Text.Length = 0 Then
            MessageBox.Show("パスワードとIDを両方記入して下さい""エラー!")
        Else
            If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
                Me.DialogResult = Windows.Forms.DialogResult.OK
                Me.Close()
            Else
                MessageBox.Show("入力した情報が間違っています。再度入力して下さい。""エラー!")
            End If
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs String
        Dim RESULT As String 
        Dim SQL As String = "SELECT ユーザー名 FROM ユーザー情報 WHERE ユーザーID='" & Replace(id, "'""''") & "' AND パスワード='" & Replace(pass, "'""''") & "'"

        Using conn As New SqlConnection(ConnectString)
            Dim CMD As New SqlCommand(SQL, conn)
            Try
                conn.Open()
                RESULT = Convert.ToString(CMD.ExecuteScalar()) 
                With UserInfo
                    .ID = id 
                    .UserName = RESULT
              '.ID,.UserNameに「Login.UserInfoのメンバーではありませんと青い波線でエラー表示  
                End With
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using
        Return RESULT IsNot Nothing 
    End Function

    Private Sub btnQuit_Click(sender As System.Object, e As System.EventArgs) Handles btnQuit.Click
        Me.Close()
    End Sub

End Class

投稿者 HiDE-Ada  (社会人) 投稿日時 2014/6/24 16:19:14
ごめんなさい、チョンボばかりで…
 with UserInfo
   :
  end with

はクラスには使えないので、直接
  UserInfo.ID = id
  UserInfo.UserName = RESULT

として下さい。

またUserInfo() as Stringとするなら、呼び出し側も
If UserInfo(...) IsNot Nothing Then

のようにしないと、実行時に「StringからBooleanへ変換できません」のようなエラーが発生します。

そしてUserInfo.vbですが、クラスの中でクラスを定義する必要はないです。

ところで、Module1.vbはどうなっていますか?

後は、本題の取得できたユーザ名をどこでTextBox.Textに設定するかですね。
投稿者 社会人  (社会人) 投稿日時 2014/6/25 09:48:32

>HiDE-Adaさん
UserInfoを指摘通りに修正すると無事にIDとユーザー名の抽出に成功しました。(一時的にFMBaseにTextBoxを設置し、TextBoxに反映されるようにコードを変えてから実行しました)
本当に有難うございます。

>後は、本題の取得できたユーザ名をどこでTextBox.Textに設定するかですね。 
可能であればウィンドウズのタイトルバー(初期設定で「最小化、最大化、閉じる」ボタンがついてる行)に「ユーザー名が利用中」と書きたいと思っていますが、出来ないのであればFMBaseのLabelで代用し、IDはこの掲示板では話に出ていませんがデータの更新や登録する際に誰か変更したか確認する為SQLServerに保存します。

>ところで、Module1.vbはどうなっていますか?

一か所だけ分からない箇所があり、

▼Moduke1.vb▼
Module Module1
    Public Sub Main()
        Dim Base As New FMBase
        Base.Show()

        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)

        Dim account As UserInfo = Nothing

        Using login As New fmlogin()
            If login.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
                account = login.LoginAccount
            Else
                Return
            End If
        End Using

        If account IsNot Nothing Then
            Application.Run(New FMBase(※account)) 
        End If

    End Sub
End Module


※がついているaccountに青い波線で「'Public Sub New()'に対する引数が多すぎます」というエラーが発生しています。
「ここはコンストラクタを追加する」と魔界の仮面弁士さんの指示がありますが、何処に追加すれば良いのでしょうか?
修正したコードのみ下に移します。

▼FMLogin▼
Imports System.Data.SqlClient

Public Class fmlogin

    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"
    Property LoginAccount 'UserInfoのaccount = login.LoginAccuontの為に追加 
    
    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click
        If TextBox1.Text.Length = 0 Or TextBox2.Text.Length = 0 Then
            MessageBox.Show("パスワードとIDを両方記入して下さい""エラー!")
        Else
            If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
                Me.DialogResult = Windows.Forms.DialogResult.OK
                Me.Close()
            Else
                MessageBox.Show("入力した情報が間違っています。再度入力して下さい。""エラー!")
            End If
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs String
        Dim RESULT As String
        Dim SQL As String = "SELECT ユーザー名 FROM ユーザー情報 WHERE ユーザーID='" & Replace(id, "'""''") & "' AND パスワード='" & Replace(pass, "'""''") & "'"

        Using conn As New SqlConnection(ConnectString)
            Dim CMD As New SqlCommand(SQL, conn)
            Try
                conn.Open()
                RESULT = Convert.ToString(CMD.ExecuteScalar())
                UserInfo.UserName = RESULT
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using
        Return ※RESULT※ IsNot Nothing
    End Function

    Private Sub btnQuit_Click(sender As System.Object, e As System.EventArgs) Handles btnQuit.Click
        Me.Close()
    End Sub

End Class


▼UserInfo▼
Public Class UserInfo
    Public Shared Property ID As String
    Public Shared Property UserName As String
  '「Login.UserInfoのメンバーではありません」エラー対策の為、修正 
End Class


後、プログラムの実行の際に問題が無かったのですが、FMLoginの※で囲まれているRESULTに緑の波線で「変数'RESULT'は、値が割り当てられる前に使用されています。Null参照の例外が実行時に発生する可能性があります。」という警告が出ています。
これに関してはどう対処すべきでしょうか?
投稿者 HiDE-Ada  (社会人) 投稿日時 2014/6/26 17:53:00
あともう少しの感じですけど・・・
Module.vbでSub Mainを使うつもりなら
Sub Mainの
    Dim Base As New FMBase
    Base.Show()
とFMBase.vbのFMBase_Loadは不要です。
これらの代わりが、Sub Main最後の
    Application.Run(New FMBase(account))     
です。

また、UserInfo.vbは
Public Class UserInfo
    Public Property ID As String
    Public Property UserName As String
End Class    
でいいでしょう。
FMLogin.vbでLoginAccountを宣言していますが、As UserInfoを付加しましょう。
そして、LoginAccountを宣言したのだから
   UserInfo.UserName = RESULT
ではなくて、
   LoginAccount.UserName = RESULT
   LoginAccount.ID = id 'FMBaseでidを追加、更新等で使用するのでは?

問題のnew FMBase(account)ですが、これはFMBase.vbに
   private Proterty LoginAccount as UserInfo   'FMLoginのものとは別
   Sub New(ui as UserInfo)
        InitializeComponent()
        LoginAccount = ui    'FMbase.vb内では、LoginAccount.ID等で使用可
        Me.text = ui.UserName & "が使用中"    'フォームのタイトルバーに表示
   End Sub
を追加しなければなりません。
投稿者 社会人  (社会人) 投稿日時 2014/7/1 11:07:00
>HiDE-Adaさん
返事が遅くなってすみません。
指摘通りに修正しましたが、3つにエラーが増えてしまいました。
もう少し、力が貸して頂くと助かります。

▼Module1.vb▼
Module Module1
    Public Sub Main()
        
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)

        Dim account As UserInfo = Nothing

        Using login As New fmlogin()
            If login.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
                account = login.LoginAccount
            Else
                Return
            End If
        End Using

        If account IsNot Nothing Then
            Application.Run(New FMBase(account)) 'コンストラクタを追加 . 
        End If

    End Sub
End Module


▼FMBase▼
Public Class FMBase

    Private Proterty LoginAccount as Userinfo 'LoginAccount as Userinfoで「ステートメントの終わりを指定してください。」とエラー 
    Sub New(ui As UserInfo)
        InitializeComponent()
        LoginAccount = ui 'LoginAccountで「LoginAccountは宣言されていません。アクセス出来ない保護レベルになっています。」とエラー 
        Me.Text = ui.UserName & "が使用中"
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Dim aaa As New 取引先情報
        aaa.Show()
    End Sub

    Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
        Dim bbb As New 品目登録
        bbb.Show()
    End Sub
End Class


▼UserInfo.VB▼
Public Class UserInfo

    Public Property ID As String
    Public Property UserName As String
    
End Class


▼FMLogin.vb▼
Imports System.Data.SqlClient

Public Class fmlogin

    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"

    'Property LoginAccount 
    Property LoginAccount As UserInfo

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click
        If TextBox1.Text.Length = 0 Or TextBox2.Text.Length = 0 Then
            MessageBox.Show("パスワードとIDを両方記入して下さい""エラー!")
        Else
            If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
                Me.DialogResult = Windows.Forms.DialogResult.OK
                Me.Close()
            Else
                MessageBox.Show("入力した情報が間違っています。再度入力して下さい。""エラー!")
            End If
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs String
        Dim RESULT As String
        Dim SQL As String = "SELECT ユーザー名 FROM ユーザー情報 WHERE ユーザーID='" & Replace(id, "'""''") & "' AND パスワード='" & Replace(pass, "'""''") & "'"

        Using conn As New SqlConnection(ConnectString)
            Dim CMD As New SqlCommand(SQL, conn)
            Try
                conn.Open()
                RESULT = Convert.ToString(CMD.ExecuteScalar())

                LoginAccount.UserName = RESULT
                LoginAccount.ID = id

            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End Using
        Return RESULT IsNot Nothing
    End Function

    Private Sub btnQuit_Click(sender As System.Object, e As System.EventArgs) Handles btnQuit.Click
        Me.Close()
    End Sub

End Class

投稿者 社会人  (社会人) 投稿日時 2014/7/1 11:08:12

※入力制限で二つに分けて書き込みします。

▼Application.Designer.vb▼
'------------------------------------------------------------------------------ 
' <auto-generated> 
'     このコードはツールによって生成されました。 
'     ランタイム バージョン:4.3039. 

'     このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 
'     コードが再生成されるときに損失したりします。 
' </auto-generated> 
'------------------------------------------------------------------------------ 

Option Strict On
Option Explicit On

Namespace My
    
    'メモ: このファイルは自動生成されました。直接変更しないでください。変更したり、 
    ' ビルド エラーが発生した場合は、プロジェクト デザイナー へ移動し (プロジェクト 
    ' プロパティに移動するか、またはソリューション エクスプローラーのマイ プロジェクト 
    ' ノード上でダブルクリック)、アプリケーション タブ上で変更を行います。 
    
    Partial Friend Class MyApplication
        
        <Global.System.Diagnostics.DebuggerStepThroughAttribute()>  _
        Public Sub New()
            MyBase.New(Global.Microsoft.VisualBasic.ApplicationServices.AuthenticationMode.Windows)
            Me.IsSingleInstance = false
            Me.EnableVisualStyles = true
            Me.SaveMySettingsOnExit = true
            Me.ShutDownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterMainFormCloses
        End Sub
        
        <Global.System.Diagnostics.DebuggerStepThroughAttribute()>  _
        Protected Overrides Sub OnCreateMainForm()
            Me.MainForm = Global.Login.FMBase 'Global.Login.FMBaseで「FMBaseはLogin型です。式として使用することはできません。」 
        End Sub
    End Class
End Namespace



投稿者 HiDE-Ada  (社会人) 投稿日時 2014/7/1 16:29:10
Private Proterty LoginAccount as Userinfo
ではなくて
Private Property LoginAccount as Userinfo
でした。アナガアッタラハイリタイ;

Application.Designer.vbのソースを直接編集することはないので
もしかすると、スタートアップが
Sub Main
に変更すればいいんじゃないでしょうか?
投稿者 社会人  (社会人) 投稿日時 2014/7/1 17:25:57
>HiDE-Adaさん

有難うございます。
修正したらエラーが一気に消えましたが、ログイン画面の「btnGo」を押すと「オブジェクト参照がオブジェクトインスタンスに設定されていません。」というメッセージ画面が表示されて先に進みません。

後、プログラムを実行する際に影響が無かったのですが、FmLoginの「Return RESULT IsNot Nothing」でRESULTに緑の下線で「変数RESULTは、値を割り当てられる前に使用されています。Null参照の例外が実行時に発生する可能性があります。」と書かれていますが、無視しても大丈夫でしょうか?
投稿者 ふるポテ  (社会人) 投稿日時 2014/7/1 17:54:17
>後、プログラムを実行する際に影響が無かったのですが、FmLoginの「Return RESULT IsNot Nothing」
>でRESULTに緑の下線で「変数RESULTは、値を割り当てられる前に使用されています。Null参照の
>例外が実行時に発生する可能性があります。」と書かれていますが、無視しても大丈夫でしょうか?

無視してもいいと思ってるならすりゃいいじゃねーか
なんでワーニングにまで他人任せなんだよ、自分の書いてるソフトだろうに

ワーニングとしては”RESULTが初期化されず、コードの動いてくるルートによっては何も入らずにその行にたどり着く可能性があるけどソレでもいいのか?”って聞かれてるの。
それについて自分でどう思うんだ?

なんでも「どうですか?」って聞かずに考えろよ
投稿者 HiDE-Ada  (社会人) 投稿日時 2014/7/2 18:06:05
たしかに、なんでもかんでも回答を書くって言うのはよくないかもですが、
…まったく回答解説のない問題集っていうのは、使えないかなと思ってしまうので^^;

>オブジェクト参照がオブジェクトインスタンスに設定されていません。
もエラー箇所はたぶん
LoginAccount.UserName = RESULT
じゃ、ないでしょうか?
 Property LoginAccount As UserInfo
が問題で、自分が「As UserInfo」を付加しましょうと書いたからですね。
これは宣言(…定義?)だけになるので、
 Property LoginAccount As New UserInfo
と書くか(…エラーはでないので問題ないはず;)、LoginAccountを使用する前に
 LoginAccount = new UserInfo
が必要です。

RESULTの警告も
 Dim RESULT As String
なので、初期値(デフォルトはNothing?)が設定されていないうえ
 RESULT = ...
と値を設定しているのが、IF文がTRUEの場合だけだから、
値のない場合があるRESULTを使ってもいいの?というものです。
今回はその未設定を判断しているので問題ないですが、
気になるなら、
 Dim RESULT As String = Nothing
としておきましょう。

VBも大きくなってきて、同じことを処理する記述が複数書けるようになった
きたのは少し問題かなと思ってます。
それぞれのコードの意味を考えて、プログラムしてください。
ただ単に言われたとおりに書いてるだけでは、次のときにまた悩むことになると思います。
投稿者 社会人  (社会人) 投稿日時 2014/8/7 14:45:17

一か月返事をせずにすみません。
どうにか、データベースプログラムの大半を完成させる事が出来ました。
細かい修正などが多く残っていますが、ログイン関係の問題も解決しました。
質問に答えて下さった方に大変迷惑をかけました。本当に有難うございます。

書いて頂いたコードに修正を加えましたので、記載します。

▼FmLogin▼
修正理由
修正前はSQLServerに無い情報でもIDとパスワードを適当に入力するだけでログインが出来た為、
RESULTを基準にSQLserverにあるデータのみログイン出来る用にしました。

Imports System.Data.SqlClient

Public Class fmlogin
    Const ConnectString As String = "Data Source=user;Initial Catalog=データベースシステム;Integrated Security=True"
    Property LoginAccount As New UserInfo
    Dim er As Double = 0

    Private Sub btnGo_Click(sender As System.Object, e As System.EventArgs) Handles btnGo.Click

        er = 0

        If TextBox1.Text.Length = 0 Or TextBox2.Text.Length = 0 Then
            MessageBox.Show("パスワードとIDを両方記入して下さい""エラー!", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Else
            If CheckUser(TextBox1.Text.Trim, TextBox2.Text.Trim) Then
                If er = 0 Then
                    Me.DialogResult = Windows.Forms.DialogResult.OK
                    Me.Close()
                Else
                    MessageBox.Show("入力した情報が間違っています。再度入力して下さい。""エラー!", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    TextBox1.Select()
                End If
            End If
        End If
    End Sub

    Private Function CheckUser(id As String, pass As StringAs String
        Dim RESULT As String = ""
        Dim SQL As String = "SELECT ユーザー名 FROM ユーザー情報 WHERE ユーザーID ='" & Replace(id, "'""''") & "' AND パスワード='" & Replace(pass, "'""''") & "'"
        Using conn As New SqlConnection(ConnectString)
            Dim CMD As New SqlCommand(SQL, conn)
            Try
                conn.Open()
                RESULT = Convert.ToString(CMD.ExecuteScalar())

                LoginAccount.UserName = RESULT 
                LoginAccount.ID = id

            Catch ex As Exception
                MsgBox(ex.Message)
            End Try

            If RESULT = "" Then
                er = 1
            End If

        End Using
        Return RESULT IsNot Nothing
    End Function


▼FmBase▼
修正理由
データを登録したり修正した際にSQLに保存されるユーザーIDが保存されない為、※印を追加

 Private Property LoginAccount As UserInfo
    Sub New(ui As UserInfo)
        InitializeComponent()
        LoginAccount = ui
        Me.Text = ui.UserName & "が使用中"
        UserInfo.ID = ui.ID ※
    End Sub